Optional arguments in C function

60,435

Solution 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).

Solution 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.

Solution 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;
}
Share:
60,435
Eray CANLI
Author by

Eray CANLI

Updated on November 14, 2020

Comments

  • Eray CANLI
    Eray CANLI over 3 years

    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?

    • Jeegar Patel
      Jeegar Patel over 9 years
      so you want to check 1) argument is present or not or 2) the value of argument?
    • Eray CANLI
      Eray CANLI over 9 years
      Want to check if argument is present or not.
    • Paul R
      Paul R over 9 years
      See eskimo.com/~scs/cclass/int/sx11b.html for an example of how to implement a printf-like function.
  • Neil
    Neil over 4 years
    Very good answer, plus your comment on 'write' over 'read/write' make me grin as I will have to borrow that in the future.
  • Edenia
    Edenia over 4 years
    @AndrewHenle I prefer #define MFUNC(x, args...) wrapped_func(x, args). It works on my Cygwin GCC, but not across different compilers and versions to my knowledge. args will expand to the arguments passed in a variadic manner. I already mentioned that standard-compliant approaches should be preferred and used so I don't see why it should be repeated.
  • Edenia
    Edenia over 4 years
    ..But for the sake of argument while C does not explicitly mention what is stored where regarding function arguments (they can be stored on stack or live in register - if they are not wider than the requirement for this), it says that a C function has an activation record and confronting the newer version gcc standard, you can be pretty sure that parameters will be in adjacent memory if you request them to be so. But usually such things used to be assisted by the platform specification.
  • Andrew Henle
    Andrew Henle over 4 years
    It works on my Cygwin GCC No, it doesn't "work". You just haven't observed it failing yet. It wouldn't surprise me if the code fails if it's compiled with aggressive optimization options you can be pretty sure that parameters will be in adjacent memory You've never used non-x86 systems, have you? (*((type* )(ptr+=sizeof(ptr))) will almost certainly violate 6.3.2.3 Pointers, paragraph 6 of the C standard on hardware with any alignment restrictions.
  • Edenia
    Edenia over 4 years
    @AndrewHenle I am using 3 completely different systems actually. I tested and it worked on all of them. The common between them is that they are all windows (except the debian boot). Hence those rules apply: docs.microsoft.com/en-us/cpp/build/… (Also variable-argument macros were introduced in 1999 in the ISO/IEC 9899:1999 (C99)) I already mentioned that the code is for experimental purposes only. It is platform-specific non-standard code and stdarg should be used instead.
  • Edenia
    Edenia over 4 years
    What are you referring to in 6.3.2.3 N748 ? - N759 If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined? Because with anything that has to do with alignment there is nothing "certain". Alignment attributes can be used in the slightest.
  • Andrew Henle
    Andrew Henle over 4 years
    I tested and it worked on all of them. No, it doesn't "work". You are confusing "not observed to fail" with "works". You just haven't observed it failing yet. You can not safely play games with pointers like that.
  • Edenia
    Edenia over 4 years
    I am not confusing it, I am just not using the correct words. Nonetheless, I mean that the results were good. Of course, it still does not mean the behavior is well-defined. I did not expect it to "work". I experimented. There is nothing wrong with that. Also, the calling convention, the underlying platform ABI specification and the compiler specification does not hint that this will not "work" in the context of ellipsis argument passing. Of course, you are not wrong, but I already gave a warning. Do you want me to remove that part of the question?