C: prefer f(…) over (*f)(…)

2007-04-30

In C all function calls take place via a function pointer. It says so in section 6.5.2.2: “Constraints: The expression that denotes the called function shall have type pointer to function returning void or returning an object type other than an array type.”.

So what happens when you write p = malloc(sizeof *p);? malloc isn’t a function pointer, it’s a function. What happens is that when a function is used in an expression it is automatically converted to a pointer to the function instead (section 6.3.2.1); except for sizeof where it is illegal, and & (address-of).

That means that if callback is already a function pointer you don’t need to go (*callback)() to call it, you can just go callback(). Most of the time I prefer that. It’s neater because you don’t need the * so you don’t need the parentheses either. It also shows confidence; it shows that you know what you’re doing.

If do you go (*callback)() then dereferencing the pointer yields a function, which is automatically converted back into the function pointer. It all seems a bit pointless. An amusing consequence, which not that many C programmers seem to realise, is that you can have as many *s as you like:

(**********callback)();
About these ads

11 Responses to “C: prefer f(…) over (*f)(…)”

  1. rk Says:

    Sure, in an expression, F->FP always. But not in certain declarations: the “*”s are required in the “typedef struct” line below, for struct members and arrays. I was surprised that one wasn’t required for the function-paramtere in the “corge” line.

    #include
    void foo(void) { printf(“foo!\n”); }
    typedef struct bar { void (*baz)(void); void(*quux[1])(void); } bar;
    void corge(void grault(void)) { grault(); }
    int main(void)
    {
    bar b; b.baz = foo; b.quux[0] = foo;
    b.baz(); (***********b.quux[0])(); corge(foo);
    return 0;
    }

    So I can see that FP could become popular/recommended as good style in invocation (even though unnecessary), in order to make it match the FP in declarations.

  2. drj11 Says:

    The parameter type list in a function declaration also has a sort of automatic conversion going on. See section 6.7.5.3: «A declaration of a parameter as ‘‘function returning type’’ shall be adjusted to ‘‘pointer to function returning type’’,as in 6.3.2.1.». The same thing goes for array types too.

    You can make use of this function / function pointer slipperyness to make argument declarations mimic function declarations. Consider qsort:

    void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

    We could define a typedef for the compar argument:
    typedef int CompareF(const void *, const void *);

    Note that this is a function type, not a pointer type. Because of the function type to pointer type conversion that applies to argument declaration lists we can still use this in the declaration of qsort:

    void qsort(void *base, size_t nmemb, size_t size, CompareF compar);

    We can then also use this typedef in declaring functions:

    CompareF CompareInt;
    CompareF CompareComplex;

    Though, slightly disappointingly, we can’t use it when defining the functions. We have to write out the type:

    int CompareInt(const void *l, const void *r) { …

    Note that if the typedef had been to the pointer type:

    typedef int (*CompareFP)(const void *, const void *);

    then we could use it in declaring qsort, but we couldn’t use it to declare a comparison function.

    As you note function type declarations in other places, such as structs, aren’t subject to conversion to pointer. So if we want a comparison function in a struct then we have to use the pointer type explicitly:

    struct foo { CompareF *compare; };

  3. glorkspangle Says:

    Source code is primarily for communication with other programmers. I wonder whether f(); (*fp)(); is better for that purpose than f(); fp();

  4. glorkspangle Says:

    Added to which, what the heck is (**************fp)() supposed to mean? What type does the sub-expression ***fp mean? What type does it have?

  5. drj11 Says:

    When I wrote the original article I thought about whether I should say anything about the relative communicative strengths of (*fp)(); versus f();. I almost did. And what I would have said is that I find the name used to be a more powerful indicator. Most of the time when you use a genuine function pointer to call a function it’s either a structure member or an argument. If it’s a structure member then obj->method() is instantly clear. If it’s an argument then it’s less clear, but local coding standards ought to give a clear distinction between argument names and global function names. Often the name would be “callback” or “compare” or “f” or something else that’s obviously not a global function.

    I think that C is creating an unnecessary distinction. In languages like ML when I go f x it doesn’t matter whether f is a real function or f is a “variable” bound to a function value. In ML you really can’t tell anyway.

    So the style of preferring f() tries to paper over the distinction again. I’m not sure whether that’s a good thing or a bad thing.

  6. drj11 Says:

    As to your “(**************fp)()” question, I can’t tell whether you’re confused as to what the C standard says on this matter (seems unlikely) or as to what I intend it to commnicate.

    What I intend to communicate is surprise (to those people that don’t accurately know about automatic function to function pointer conversion). I wouldn’t ever advocate it in real code, just as an illustration of what the C standard has to say about functions being converted to function pointers.

  7. glorkspangle Says:

    In ML there’s no distinction to be made: a value of function type is a value of function type.
    Agree that obj->method() is right.
    Must dig out a copy of the C standard. Which one are you using?

  8. drj11 Says:

    C99 (in the infosys).

    Yes ML the language makes no distinction (yay!), but people programming in ML do. Either by the longevity of the binding or by the length of its name.

  9. glorkspangle Says:

    What I was forgetting is paragraph 4 of 6.3.2.1:
    Except when it is the operand of the sizeof operator or the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’.
    That is true anywhere. And when you see the expression *fp, that’s a function designator (6.5.3.2 para 4). So it gets automatically converted to a function pointer. To which one can apply * again, getting a function designator which is etc etc.
    I’m just very rusty on this insanity, which is the inevitable consequence of the idea of function pointers as a separate type from functions. Is there any benefit from that idea?
    I’m not sure that people programming in ML do make that distinction. I’ve exploited the lack of distinction many times in the past (e.g. by replacing the definition of fun frob_the_spong x by val frob_the_spong = make_a_frobber y). It’s true that many ML functions have ‘local variables’, short-lived local bindings of short names. But ‘global functions’ are often introduced by val rather than fun.

  10. drj11 Says:

    As a consequence of this discussion I was wondering yesterday why C had both functions and function pointers and whether we could unify them again.

    Currently when a C compiler sees the declaration “T f;” and T is a function type it can generate code to call f directly (it can emit a CALL instruction with an immediate operand (fixed up by the linker)). If instead f is a function pointer: “T *f;” then the value of f has to be loaded into a register and then called via the register. It’s a different (and probably slower) calling sequence.

    If we unify functions and function pointers then the compiler can’t tell the difference between the two cases. If I see “T f;” in a header file I can’t tell whether that’s a variable f that refers to a function or an actual definition of a function called f. So there had better not be any actual difference. So I would propose that “T f;” declares a variable (not necessarily modifiable) that refers to a function.

    For branch prediction it seems beneficial to generate a CALL to an immediate address where possible. This would be possible if _defining_ a function called f of type T meant that the variable f could not be modified (undefined behaviour). That at least gives the linker the opportunity to spot what is going on and modify the call site to use a constant address.

    Of course since this now makes functions more like an object type you’d still have pointers to functions, but they’d be more like ordinary pointers, and I don’t propose that you would be able to call through them for free.

  11. glorkspangle Says:

    1. Isn’t this just some sort of aliasing analysis?
    2. Can we do this on top of existing ABIs/linkers?
    3. Can we unify them while still obeying the standard?

    As a fundamental question about languages, you can certainly do aliasing/dataflow analysis to turn f() into a CALL (where f is of a unified type combining functions and pointers to functions). Mature implementations of languages with closures (such as ML) do that a lot (although I’m not sure how much we did in MLWorks, where our focus was on implementing the plain language fast, rather than on clever analyses to transform the semantics).

    I guess there are several things we need to do:

    – implement f() as a call, using aliasing/dataflow analysis.
    – decide what to do about f=g
    – and what about f=NULL.

    Further questions: what if f is in another library?

    I note that many C implementations have magic entry code for calls across libraries or linkage units or whatever. So we can afford for that inter-unit entry to call through a slot (and the branch prediction won’t care, because it’ll be to the same target 99.99999% of the time for that interior call site).

    Sorry, rambling.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: