Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typesafe varargs in C with gcc

Many times I want a function to receive a variable number of arguments, terminated by NULL, for instance

#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);
}

Can I instruct gcc or clang to warn if foo receives non char* variables? Something similar to __attribute__(format), but for multiple arguments of the same pointer type.

like image 767
mikebloch Avatar asked May 10 '12 12:05

mikebloch


2 Answers

I know you're thinking of using __attribute__((sentinel)) somehow, but this is a red herring.

What you want is to do something like this:

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

which wraps:

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

which you can write exactly the way you would hope you can write it!

Then you can call:

push(stack, "foo", "bar", "baz");
push(stack, "quux");
like image 113
geocar Avatar answered Oct 13 '22 23:10

geocar


I can only think of something like this:

#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;
}
like image 21
Alexey Frunze Avatar answered Oct 14 '22 00:10

Alexey Frunze