Подтвердить что ты не робот

Типы варвардов в C с gcc

Много раз я хочу, чтобы функция получала переменное количество аргументов, завершенных NULL, например

#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 или clang для предупреждения, если foo получает переменные non char*? Что-то похожее на __attribute__(format), но для нескольких аргументов одного и того же типа указателя.

4b9b3361

Ответ 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");

Ответ 2

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

#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 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

Проблема с вариаторами 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, похоже, остается неуловимым.