Keyword parameters

Discussion in 'C Programming' started by James Harris, Jun 1, 2014.

  1. No, strtod() isn't variadic. You can write any of the following:
    strtod("42", NULL);
    strtod("42", (void*)NULL);
    strtod("42", 0);
    strtod("42", (void*)0);
    and the argument will be implicitly converted to char**.
    Keith Thompson, Jun 4, 2014
    1. Advertisements

  2. Oops. Shouldn't post in a hurry.

    -- Richard
    Richard Tobin, Jun 4, 2014
    1. Advertisements

  3. James Harris

    James Harris Guest

    I know this subthread was, as Noob mentioned above, going off on a tangent
    but the discussion seems to well illustrate that keywords would be better
    identified by integers rather than strings. Some reasons:

    * Zero could be used as the terminator.
    * Non-zeroes could identify keywords.
    * Keyword ids could be provided as header constants.
    * And/or they could be provided as exported globals.
    * Selection by a switch statement would be fast and easy

    Maybe something like the following?

    va_start(k, unsigned);
    while (key_id = va_arg(k, unsigned)) { /* Iterate over keywords */
    switch (key_id) {
    case A:
    /* Use va_arg to fetch value(s) for keyword A */
    case B:
    /* Use va_arg to fetch value(s) for keyword B */
    ... etc ...
    /* Error - invalid key id */

    By contrast strings would require a series of strcmp() operations or
    something quite complex like trie matching.

    Of course, there is also the struct-based approach that has been mentioned
    for later versions of C, and the option of passing the address of a struct.
    Which choice turns out to be "best" may depend on a number of factors but
    ISTM that there are some good ways in C to support keyword args. Cool!

    James Harris, Jun 4, 2014
  4. James Harris

    James Kuyper Guest

    On 06/04/2014 10:40 AM, Noob wrote:
    Exactly. void* is not compatible with double*. It's convertible to and
    from double*, but it doesn't necessarily have the same representation,
    alignment, or even necessarily the same size. There have been real
    implementations where pointers to types that are word-aligned are
    smaller than pointers to types that are not.
    James Kuyper, Jun 4, 2014
  5. James Harris

    BartC Guest

    For short string matching I've used char literals such as 'abcd' or
    'abcdefgh'. These can't be compared quite as quickly as compact enum codes,
    but it's faster than string matching. Also you don't need to maintain a set
    of enums for each function you want to use keyword parameters with; you just
    use the parameter name (or a version of it to fit within the 4- or
    8-character limit).

    But, I think C doesn't like you using multi-char constants like this (trying
    it now, gcc doesn't even like more than 4 characters), which limits the use
    of this method.
    BartC, Jun 4, 2014
  6. The standard doesn't even guarantee that 'ab' and 'ba' have distinct
    values; it only says that their values are implementation-defined
    (and of type int). On the other hand, they'll be distinct in any
    sane implementation, and assuming that they'll be distinct doesn't
    particularly bother me. (Assuming they have some particular value
    would be unwise if your code is supposed to be at all portable.)

    gcc warns about all multi-character character constants by default
    (a reasonable warning IMHO, but not required by the standard).
    It issues a different warning for multi-character constants of more
    than 4 characters (where sizeof (int) == 4) -- but that warning is
    also not required, or even suggested, by the standard.

    For this program:

    #include <stdio.h>
    int main(void) {
    printf("'a' = 0x%x\n", (unsigned)'a');
    printf("'ab' = 0x%x\n", (unsigned)'ab');
    printf("'abc' = 0x%x\n", (unsigned)'abc');
    printf("'abcd' = 0x%x\n", (unsigned)'abcd');
    printf("'abcde' = 0x%x\n", (unsigned)'abcde');

    gcc produces the following warnings:

    c.c: In function 'main':
    c.c:4:42: warning: multi-character character constant [-Wmultichar]
    c.c:5:42: warning: multi-character character constant [-Wmultichar]
    c.c:6:42: warning: multi-character character constant [-Wmultichar]
    c.c:7:42: warning: character constant too long for its type [enabled by

    and this output:

    'a' = 0x61
    'ab' = 0x6162
    'abc' = 0x616263
    'abcd' = 0x61626364
    'abcde' = 0x62636465

    The program is perfectly legal, and any conforming implementation must
    accept it. Its output is implementation-defined.

    I wouldn't use multi-character constants myself other than to find out
    what a compiler does with them.

    I've seen them most often in code written by inexperienced programmers
    who haven't yet learned the difference between single and double quotes.
    Keith Thompson, Jun 4, 2014
  7. And because foob is variadic (more precisely, because (void *)0 is
    passed as an argument corresponding to the ", ..." in the declaration),
    no conversion takes place. The caller passes an argument of type void*,
    and foob *assumes* that it's of type double*.

    "If you lie to the compiler, it will get its revenge." -- Henry Spencer
    Keith Thompson, Jun 4, 2014
  8. Something I missed in my previous followup: Why do you think
    comparisons of values like 'abcd' would be slower than comparisons
    of "compact enum codes"? Non-wide character constants are always
    of type int; a literal 'abcd' is likely to be exactly equivalent
    (in value and type) to a literal 1633837924 (for example).

    A comparison of a single-byte literal like 'a' *might* be optimized to
    an 8-bit comparison -- but only if byte comparisons are actually faster
    than word comparisons on the target hardware.
    Keith Thompson, Jun 4, 2014
  9. James Harris

    BartC Guest

    By 'compact' I mean the values needed to represent the set of parameter keys
    occupy a narrow range, such as 0 to 13 when there are 14 keys.

    Given such a key, a switch can use a jump table to go straight to the
    handler for that key.

    With multi-char literals, it is necessary to test them one by one. (This can
    be optimised a little testing the most likely ones first, but that's about
    it. It won't be worthwhile using binary searches, hashing and such, which
    are still not going to be as fast as a switch.)

    (There might also be a way to use some compile-time macro to effectively
    hash any multi-char value, or even a regular string, into a compact ordinal
    value. But that gets complicated and the keyword code gets more cluttered.)
    BartC, Jun 4, 2014
  10. Realistically you've got to use an if .. else ladder. It's got the same
    visual complexity as a switch. But if you're worried about the comparatively
    minor overhead, probably you shouldn't be coding named arguments anyway.

    Strings aren't efficient. But they have the advantage that you can
    always provide something short and humanly meaningful, like "x" (you
    can't realistically #define x). They don't pollute namespace, it's
    obvious what type they are, and you can print them out for debugging
    and get meaningful values. Also, its hard to inadvertently pass
    a wrong value (MYLIB_CURSOR_X as opposed to MYLIB_CURSOR_XPOS in
    a related function).
    If the practice catches on, then as most named arguments will be passed
    as string literals, a compiler can optimise the strcmp() calls to
    Malcolm McLean, Jun 4, 2014
  11. They don't pollute namespace because they have no namespace. String
    literals make it (nearly) impossible for the compiler to catch typos.
    gcc can detect many errors involving printf-style format strings, but
    only because that particular syntax is hardwired into the compiler; that
    won't happen for any literal values you invent for your own program.

    And I don't see how using string literals rather than identifiers makes
    it harder to inadvertently pass an incorrect value. In addition to the
    existing risk of using the wrong name, the add the risk of passing an
    incorrect spelling of the right name.
    Keith Thompson, Jun 4, 2014
  12. James Harris

    BartC Guest

    That's true. Although you can still inadvertently type the name of a
    parameter from a different function (or even something that isn't a
    parameter keyword name).

    But the point about the namespace is valid. If you have many functions (a
    suite of functions in a library for example) that tend to use the same
    parameter names (X,Y or Z, or Filename, Caption, Result etc), then you need
    to think up different names for each function, or create some scheme where a
    bunch of functions can share the same core keyword names.

    With strings or multi-char constants, that is not an issue. (Of course, even
    better would be to have actual keyword parameters; they're straightforward
    to implement, and simpler to do so than designated initialisers. Why would
    something so obviously useful be left out of a major new version?)
    BartC, Jun 4, 2014
  13. .... because they are not names, but just values.
    Seungbeom Kim, Jun 4, 2014
  14. James Harris

    Noob Guest

    Roger that.

    C is a sneaky treacherous little bastard. Every time I think I have
    (most of) it nailed down, I get smacked upside the head.

    Last time, it was the huge pile of FAIL that is the ctype.h API.
    (Which requires one to cast the parameter.)

    Noob, Jun 5, 2014
  15. James Harris

    Noob Guest

    FWIW (not much), I would guess that a majority of implementations define
    NULL as ((void *)0)
    OK, thanks to Ben, James and you for clearing that confusion of mine
    (between compatible and convertible).
    Funny you should mention execl, because that is specifically the
    function I had in mind, and I could have sworn that the docs did
    not specify the type for NULL.

    I guess it could be considered a defect in the man-pages entry.

    .... because POSIX did indeed specify it:

    I am pretty sure it was not specified in the 2001 document...

    Noob, Jun 5, 2014
  16. Perhaps; I haven't done a survey. gcc and clang use ((void*)0) (yes,
    <stddef.h> is provided by gcc, not by a separate library). Solaris cc
    uses 0. But either definition is legal, and it's unwise to write code
    that assumes one or the other.
    Quoting the page you cited:

    The list of arguments must be terminated by a null pointer, and,
    since these are variadic functions, this pointer must be cast
    (char *) NULL.
    It is in the 2004 version:
    I haven't found the 2001 version online, but I'd be surprised if
    it didn't mention the cast somewhere.

    Incidentally, POSIX requires NULL to expand to "an integer
    constant expression with the value 0 cast to type void *", a
    stricter requirement than ISO C, which doesn't require the cast.
    Of course 0 is still a null pointer constant.
    Keith Thompson, Jun 5, 2014
  17. What, I have to use pointer types consistently? What a sneaky
    treacherous little bastard! (Sheesh.)
    I agree that's unfortunate. There are historical reasons for it.
    Prior to the introduction of prototypes in C89, it wasn't possible to
    specify a parameter of type char, so the is*() and to*() functions
    had to take arguments of type int. Combine that with the requirement
    to accept EOF as an argument.

    I wouldn't call it a "huge pile of FAIL", though, just an annoyance.

    In the old days, programmers probably didn't bother with the cast.
    On EBCDIC systems, plain char is unsigned and omitting the cast
    is harmless. On ASCII systems, the cast matters only for negative
    values, and such values would not appear in ordinary text.
    Keith Thompson, Jun 5, 2014
  18. The price of backwards compatibility, unfortunately. This can help if
    you have a lot of code that does these tests:

    #define IS(what, c) (is##what((unsigned char)(c)))
    Ben Bacarisse, Jun 5, 2014
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.