Optional arguments in C function

2020-06-03 02:57发布

问题:

In a C function, I want to check if an input argument ('value' in my case) is presented or not.

i.e.:

void Console(char string[], int32_t value)
{
    // write string here
    // write value here, if it exists
}

When used if(value != NULL) statement, my Console() function sends 4096

How can I check and act based on argument existence?

回答1:

Optional arguments are generally not allowed in C (but they exist in C++ and in Ocaml, etc...). The only exception is variadic functions (like printf). 

Historically, the open(2) function from POSIX accepted in some cases an optional third argument (at the time it was defined - in the 1970s and 1980s -, the calling conventions practically pushed arguments on the call stack, so ignoring that argument was simple to implement). If you look today at recent implementation of that open function in free software libc implementations on Linux, such as musl-libc, you see in its src/fcntl/open.c that it uses the <stdarg.h> variadic facilities (which are often implemented as compiler builtins).

BTW, you could define some macros to fill the "missing" arguments, so if you have

  void console(const char*, int32_t);

you might also

  #define console_plain(Msg) console((Msg),0)

and that could be instead some inline function in some header, e.g.

  static void inline console_plain (const char*msg) 
  { console(msg, 0); }

then use console_plain("hello here") elsewhere

Then your variadic function should define how and what arguments are allowed (after a non-empty sequence of fixed arguments). And use stdarg(3) to get these variadic (actual) arguments.

The actual arguments are known mostly at compile-time, not at run-time. So you need a convention which often defines which variadic argument are permitted from the required fixed arguments. In particular, you have no way to test that an argument is present (that information is lost at runtime).

BTW, with variadic functions you generally lose the typechecking that most C compilers provide (at least when you enable all warnings, e.g. gcc -Wall -Wextra). If using GCC you might have some function __attribute__-s (like format, sentinel, ....) in the prototype to assist that. You could even customize gcc with the obsolete MELT, or in 2019 with your GCC plugin, to add your own attributes doing their own type checking.

How can I check and act based on argument existence?

With current usual calling conventions (e.g. study the x86-64 ABI) you generally cannot do that (without using variadic functions).



回答2:

This sounds as if you are trying to use hackery to potentially facilitate yourself or whomever opened your source code "in write mode". I say "in write mode" (not read/write) because such code is very hard to read, because of all the hidden code, because of the magical macros you would need. C is not a smart language. The compilers might be smart, but the language semantics are rather pedantic. You should strictly comply with the rules, otherwise you are risking to make bad software or worse - not working at all.


The "correct" way to do this, without creating a whole new programming language is to provide two arguments, and when the second should not be used, it should either be passed as NULL if it is a pointer or some excluding number such as -1 if it is a numeral type.

Another approach is to create two separate functions with hinting names such as: console and console_full. Respectively with one and two arguments.

But if you are still not comfortable with the aforementioned approaches, you can include stdarg.h to do it for you.

void Console (char *string, ...) /* Note the ... */
{
    va_list param;
    int32_t optValue = (-1); /* -1 would indicate optValue is to be ignored */

    // write string here

    va_start(param, string);

    optValue = va_arg(param, int32_t);

    if(optValue != (-1))
    {
        /* Work with `optValue` */
    }

    va_end(param);
}

Which way is not good, because you don't know the types of the additional arguments, neither you know how many are they. To know those things, you should do as printf-alike functions do, parse specific tokens inside the string that suggest an argument exists and what type of argument it is or at least just use a const variable counter of arguments. You can macrofy further that the counting of arguments is automatic.


Update:

You don't really need stdarg to use variadic functionality in C as this is a built-in feature. The C pre-processor can also expand variadic arguments (Although probably not in all versions). The libary stdarg provides those helpful macros, but you can implement them on your own as well. Note that not using a standard library is almost always the wrong thing. Just grab the address of the first (reference) variable, advance it with the size of the pointer and you have the address of the next argument (presumably). Something like:

#include <stdio.h>

#define INIT_VARARG(x,ref)          void* x = &ref
#define GET_NEXT_VARARG(ptr,type)   (*((type* )(ptr+=sizeof(ptr))))

void func (int ref, ...) // ref must be the number of int args passed.
{
    INIT_VARARG(ptr,ref);
    int i;

    for(i = 0; i < ref; i++)
    {
        printf("[%i]\n", GET_NEXT_VARARG(ptr, int));
    }
}

int main (void)
{
    func(3, 10, 15, 20);

    return 0;
}

You may use this for experimental purposes only. A good, secure and well-designed C code should not have such macros and should use stdarg instead.



回答3:

If you want to distinguish between function calls that take either one or two arguments, you can use macros.

While you can reproduce your desired behaviour, there are some things to note:

  • The macro implentation hides the overloading to casual readers of your code who can't see that Console is a macro. C is much about seeing the details, so if you have two different functions, they should probably get different names, maybe cons_str and cons_str_int.

  • The macro will generate a compiler error if you pass more than two arguments or if the arguments are not compatible with the required types C string and int. Which is actually a good thing.

  • Real variadic functions like printf that use the interface from <stdarg.h> must be able to derive the types and number of variadic arguments. In printf, this is done via the % format specifiers. The macro can switch between different implementations based on the number of arguments alone.

Anyway, here's an implementation. Proceed with caution.

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

#define NARGS(...) NARGS_(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define NARGS_(_5, _4, _3, _2, _1, N, ...) N

#define CONC(A, B) CONC_(A, B)
#define CONC_(A, B) A##B

#define Console(...) CONC(Console, NARGS(__VA_ARGS__))(__VA_ARGS__)



void Console1(const char string[])
{
    printf("%s\n", string);
}

void Console2(const char string[], int32_t value)
{
    printf("%s: %d\n", string, value);
}

int main()
{
    Console("Hello");
    Console("Today's number is", 712);

    return 0;
}