类型安全的可变参数用C用gcc(Typesafe varargs in C with gcc)

2019-07-29 08:26发布

很多时候,我想一个函数来接受可变数量的参数,以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或铛如果FOO接收非警告char*变量? 类似的东西,以__attribute__(format) ,但是对于相同的指针类型的多个参数。

Answer 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");


Answer 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'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;
}


Answer 3:

使用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]是指第i个键/值对的密钥value[i]是指第i个值对的值

这些文献将被编译到堆栈帧各自的地址。

这一切都不是真的很难做,也没有去过。 然而,C的设计理念根本不利于这样的功能。

如果不带头执行这个冒险的C编译器实现者(或C预处理器实现者)或类似的方案,这是不可能的,我们永远不会看到这样的事情在C.

麻烦的是人谁是兴趣类型安全和愿意投入的工作,以建立自己的编译器通常得出的结论是,C语言是超越打捞一个可能也开始了一个更好的设计的语言,开始用。

我一直在那里我自己,最终决定放弃尝试,然后实现Wirth的语言和添加型安全variadics到的一个代替。 因为我已经碰到谁向我讲述自己的中止尝试其他人。 用C正确类型的安全variadics似乎准备仍然是难以捉摸的。



文章来源: Typesafe varargs in C with gcc