Много раз я хочу, чтобы функция получала переменное количество аргументов, завершенных NULL, например
Могу ли я инструктировать gcc или clang для предупреждения, если foo получает переменные non char*
? Что-то похожее на __attribute__(format)
, но для нескольких аргументов одного и того же типа указателя.
Ответ 1
Я знаю, что вы как-то думаете об использовании __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");
Ответ 3
Проблема с вариаторами C состоит в том, что они действительно заперты после, а не на самом деле разработаны на языке. Основная проблема заключается в том, что переменные параметры анонимны, у них нет ручек, нет идентификаторов. Это приводит к громоздким макросам VA для генерации ссылок на параметры без имен. Это также приводит к необходимости указывать те макросы, где начинается список вариаций, и какой тип параметров должен иметь.
Вся эта информация действительно должна быть закодирована в правильном синтаксисе в самом языке.
Например, можно было бы расширить существующий синтаксис синтаксиса с формальными параметрами после многоточия, например
void foo ( ... int counter, float arglist );
По соглашению, первым параметром может быть аргумент count, а второй - для списка аргументов. Внутри тела функции список можно рассматривать синтаксически как массив.
При таком соглашении вариационные параметры больше не будут анонимными. Внутри тела функции счетчик можно ссылаться, как и любой другой параметр, и элементы списка можно ссылаться так, как если бы они были элементами массива параметра массива, например
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]
относится к ключу i-ой пары ключ/значение
value[i]
относится к значению пары i-го значения
и эти ссылки будут скомпилированы к их соответствующим адресам в кадре стека.
Ничего из этого действительно трудно сделать, и никогда не было. Однако философия дизайна C просто не способствует таким функциям.
Без участия разработчиков C-компилятора C (или препроцессора C), которые берут на себя инициативу для реализации этой или подобной схемы, вряд ли мы когда-либо увидим что-либо подобное в C.
Беда в том, что люди, которые заинтересованы в безопасности типов и готовы внести свой вклад в работу над своими собственными компиляторами, обычно приходят к выводу, что язык C выходит за пределы спасения, а также можно начать с более совершенного языка для начала.
Я сам был там, в конце концов решил отказаться от попытки, а затем реализовать один из языков Вирта и добавить в него безопасные вариации типа. С тех пор я столкнулся с другими людьми, которые рассказали мне о своих собственных прерванных попытках. Правильный тип безопасных вариаций в C, похоже, остается неуловимым.