2012-05-10 1 views
19

Много раз я хочу функцию, чтобы получить переменное число аргументов, заканчивающуюся NULL, напримертипизированного переменной длины в C с GCC

#define push(stack_t stack, ...) _push(__VARARG__, NULL); 
func _push(stack_t stack, char *s, ...) { 
    va_list args; 
    va_start(args, s); 
    while (s = va_arg(args, char*)) push_single(stack, s); 
} 

Могу ли я проинструктировать GCC или лязг, чтобы предупредить, если Foo получает не являющихся char* переменные ? Нечто похожее на __attribute__(format), но для нескольких аргументов одного и того же типа указателя.

+2

Если все типы должны быть одного типа, считаете ли вы, что они просто передаются в массив из них? –

+0

Нет встроенного массива в C89. Вы не можете передать, например, 'f ({1,2,3,0})' с компилятором MS C. – mikebloch

+3

вы хотите что-то, что работает с gcc или MS C? Пожалуйста, отметьте соответствующим образом. С C99 существуют решения, которые являются типичными. –

ответ

15

Я знаю, что вы думаете об использовании __attribute__((sentinel)) так или иначе, но это отвлекающий маневр.

Что вы хотите сделать что-то вроде этого:

#define push(s, args...) ({     \ 
    char *_args[] = {args};      \ 
    _push(s,_args,sizeof(_args)/sizeof(char*)); \ 
}) 

который оборачивает:

void _push(stack_t s, char *args[], int argn); 

, который вы можете написать точно так, как вы бы надеяться, вы можете записать его!

Тогда вы можете позвонить:

push(stack, "foo", "bar", "baz"); 
push(stack, "quux"); 
+0

Не вызывает ли это ненужное копирование указателей 'char *'? –

+0

@ Chi-Lan - В зависимости от реализации '_push()' GCC удалит ненужные нагрузки. – geocar

+0

Я пытаюсь сделать что-то подобное этому. Я действительно хочу __ атрибут, который гарантирует безопасность типов varargs. В основном я хочу убедиться, что они все char *. Кто-нибудь знает, как это сделать? –

1

я могу думать только о чем-то вроде этого:

#include <stddef.h> 
#include <stdio.h> 
#include <stdlib.h> 

typedef struct tArg 
{ 
    const char* Str; 
    struct tArg* Next; 
} tArg; 

tArg* Arg(const char* str, tArg* nextArg) 
{ 
    tArg* p = malloc(sizeof(tArg)); 
    if (p != NULL) 
    { 
    p->Str = str; 
    p->Next = nextArg; 
    } 
    else 
    { 
    while (nextArg != NULL) 
    { 
     p = nextArg->Next; 
     free(nextArg); 
     nextArg = p; 
    } 
    } 
    return p; 
} 

void PrintR(tArg* arg) 
{ 
    while (arg != NULL) 
    { 
    tArg* p; 
    printf("%s", arg->Str); 
    p = arg->Next; 
    free(arg); 
    arg = p; 
    } 
} 

void (*(*(*(*(*(*(*Print8 
    (const char* Str)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*) 
{ 
    printf("%s", Str); 
    // There's probably a UB here: 
    return (void(*(*(*(*(*(*(*) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*)) 
    (const char*))&Print8; 
} 

int main(void) 
{ 
    PrintR(Arg("HELLO", Arg(" ", Arg("WORLD", Arg("!", Arg("\n", NULL)))))); 
// PrintR(Arg(1, NULL));  // warning/error 
// PrintR(Arg(&main, NULL)); // warning/error 
// PrintR(Arg(0, NULL));  // no warning/error 
// PrintR(Arg((void*)1, NULL)); // no warning/error 

    Print8("hello")(" ")("world")("!")("\n"); 
// Same warning/error compilation behavior as with PrintR() 
    return 0; 
} 
+3

О брат! Я не уверен, смеяться или плакать ...;-) –

+0

@ Chi-Lan: C необычайно выразителен. :) –

+0

'Print8' - лучший и худший бит C, который я когда-либо видел. – huon

-1

Проблемы с C variadics является то, что они действительно заперты после этого, на самом деле не встроены в языке. Основная проблема заключается в том, что переменные параметры анонимны, у них нет ручек, нет идентификаторов. Это приводит к громоздким макросам VA для генерации ссылок на параметры без имен. Это также приводит к необходимости указывать те макросы, где начинается список вариаций, и какой тип параметров должен иметь.

Вся эта информация действительно должна быть закодирована в правильном синтаксисе самого языка.

Например, можно было бы расширить существующий синтаксис C с формальными параметрами после многоточие, как и

void foo (... int counter, float arglist); 

По соглашению, первый параметр может быть для подсчета аргументов, а второй для списка аргументов. Внутри тела функции список можно рассматривать синтаксически как массив.

С таким соглашением вариационные параметры больше не будут анонимными. В теле функции, счетчик может ссылаться, как и любой другой параметр и элементы списка можно ссылаться, как если бы они были массив элементов параметра массива, например, так

void foo (... int counter, float arglist) { 
    unsigned i; 
    for (i=0; i<counter; i++) { 
    printf("list[%i] = %f\n", i, arglist[i]); 
    } 
} 

С такой функцией, встроенной в сам язык , каждая ссылка на arglist[i] затем будет переведена на соответствующие адреса в фрейме стека. Нет необходимости делать это с помощью макросов.

Кроме того, счетчик аргументов автоматически будет вставлен компилятором, что дополнительно уменьшит вероятность ошибки.

Вызов

foo(1.23, 4.56, 7.89); 

будет составлен, как если бы она была написана

foo(3, 1.23, 4.56, 7.89); 

В теле функции, любой доступ к элементу за пределы фактического числа аргументов фактически переданных может быть проверяются во время выполнения и вызывают сбои во времени компиляции, тем самым значительно повышая безопасность.

И последнее, но не менее важное: все переменные параметры печатаются и могут быть проверены во время компиляции точно так же, как проверены невариантные параметры.

В некоторых случаях было бы желательно иметь чередующиеся типы, например, при записи функции для хранения ключей и значений в коллекции. Это также может быть размещен просто, позволяя более формальные параметры после многоточия, как так

void store (collection dict, ... int counter, key_t key, val_t value); 

Эта функция может затем быть названо в качестве

store(dict, key1, val1, key2, val2, key3, val3); 

но будет составлен, как если бы она была написана

store(dict, 3, key1, val1, key2, val2, key3, val3); 

Типы фактических параметров будут проверять время компиляции на соответствие формальным параметрам.

В теле функции счетчика вновь будет ссылаться по его идентификатору, ключи и значения будет ссылаться как если бы они были массивами,

key[i] относится к ключу пары ключей г-го/значения value[i] относится к значению пары i-го значения

, и эти ссылки будут скомпилированы с их соответствующими адресами на фрейме стека.

Ничего из этого действительно трудно сделать, и никогда не было. Однако философия дизайна C просто не способствует таким функциям.

Без углубляясь C компилятор Implementor (или C препроцессора Implementor), принимая на себя ведущую роль в реализации этой или подобной схеме вряд ли мы когда-нибудь увидеть ничего подобного в С.

Беда в том, что люди, которые заинтересованы в безопасности типов и готовы вложить свои работы в свои собственные компиляторы, обычно приходят к выводу, что язык C выходит за рамки спасения, и с самого начала можно начать с более совершенного языка.

Я сам был там, в конце концов решил отказаться от попытки, а затем реализовать один из языков Вирта и добавить к нему безопасные вариации типа. С тех пор я столкнулся с другими людьми, которые рассказали мне о своих собственных прерванных попытках. Правильный тип безопасных вариаций в C, похоже, остается неуловимым.

+0

Причина, по которой это не связано с синтаксисом, но изменения в ABI: например, x86_64 ABI и i386 fastcall ABI не требуют, чтобы аргументы имели «адрес», а в то время как на современных процессорах многое сделать для расходов на проливные аргументы, двойные потери драгоценного L1 делают его чистым убытком. Если вы просто хотели играть с синтаксисом и выразительностью, вы могли бы попробовать C4, поскольку он имеет свою собственную (виртуальную) ABI в любом случае: https://github.com/rswier/c4 – geocar

+0

Если этот синтаксис был разработан на языке, тогда разработчики должны были бы сопоставить их с соответствующими ABI. Нет причин, по которым вы не могли этого сделать. Ссылки разрешаются во время компиляции (по крайней мере, с точки зрения перемещаемых ссылок), и компилятор знает информацию о целевом ABI. Кроме того, нет причин, по которым список вариационных аргументов не может быть сопоставлен с набором регистров. Усилия по внедрению в значительной степени аналогичны реализации макросов VA. Реальная проблема заключается в том, что вариационные параметры анонимны. Это мешает сделать его безопасным. – trijezdci

+0

C был спроектирован вокруг ABI, а не наоборот. Если вы хотите, чтобы Pascal выглядел как C, вы могли бы построить один из c4, как я предлагаю, - он архитектурно подобен многим реализациям Pascal. – geocar