Search code examples
clanguage-lawyerfunction-pointers

When is it necessary or meaningful to write &f for a function f or *fp for a function pointer fp (except in: sizeof &f)?


From the C17 standard draft (6.3.2.1 ¶4; footnote removed):

A function designator is an expression that has function type. 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".

  • According to the footnote which I omitted from the quote above, sizeof f (for a function designator f) is non-standards-conformant.
  • Note that the C11 standard additionally mentions the _Alignof operator.
  • Note that the C2x draft additionally mentions the typeof operator.

As a result (here: f is a function designator; fp is a function pointer):

  1. One can indicate the address of a function as &f (explicit) or f (short).
  2. One can make a function call as:
    • (&f)(args) or f(args)
    • fp(args) or (*fp)(args)

Here,

  • left-hand (&f)(args)/fp(args) is consistent with function calls technically operating on function pointers, and
  • right-hand f(args)/(*fp)(args) is consistent with the naive assumption that function calls operate on functions (instead of pointers to functions).

Stylistically,

  • this answer labels &f as "always redundant",
  • (&f)(args) is virtually unused in practice,
  • f/f(args)/(*fp)(args) is the style used in K&R (2e), and
  • f/f(args)/fp(args) is the shortest style.

(For completeness' sake (and in case anyone else wants a nice summary), here is a longer list of expressions that are equivalent to f: (&f), (*******f), (***&***f), (***&**&f), (&**&***f), (&**&**&f).)

But:
When is it ever necessary or meaningful to

  • write &f (where f is a function designator) or
  • write *fp (where fp is a function pointer)?

(By "meaningful", I mean that the behavior of the program is altered.)

  • The only case I am aware of is sizeof &f. (sizeof f is illegal, even though the GCC dialect of C permits it (at least as of version 12.2.0), but with different semantics: It seemingly returns 1 in all(?) cases. Why?)
    • I don't understand this. Letting f auto-convert to &f would let us simplify the standard's phrasing and give sizeof f useful semantics too. (Perhaps this restriction exists to maintain parallelism with typeof, which has been floating around as a compiler extension for a while.)
  • Regarding what happened with _Alignof, the committee had made a mistake in C11, so they rectified it in C17. Basically: Unlike sizeof, _Alignof only accepts a parenthesized type as its argument, but not an expression (C11 draft, 6.5.3 ¶1). See also this post on SO.
  • If typeof is officially added to C with the next standard, a distinction between typeof &f/typeof fp and typeof f/typeof *fp would be obviously meaningful.

Solution

  • Since a function designator f is automatically converted to the address of the function in many situations, the only times when f is semantically different from &f is when it is not converted. Per C 2018 6.3.2.1 4, these are when it is the operand of sizeof or unary &. Thus we have:

    • sizeof f requires a diagnostic message because it violates the constraints in C 2018 6.5.3.4 1 (sizeof shall not be applied to an expression with function type) and its behavior is not defined by the C standard (because of the constraint violation), whereas sizeof &f yields the size of a pointer to the function. (sizeof f is nonetheless conforming code per C 2018 4 7, albeit not strictly conforming, since there exists a C compiler that accepts it.)
    • &f yields the address of f whereas & &f requires a diagnostic message because it violates the constraints in C 2018 6.5.3.2 1 (the operand of unary & shall be a function designator, the result of array subscripting or unary *, or an lvalue other than a bit-field or one declared with register). I am unaware of a C compiler that accepts & &f, so this is not strictly conforming code, except to the extent we could imagine and construct a C compiler that accepts it. (The space in & &f serves to separate the tokens. Otherwise && is a single token, for the logical AND operator.)

    My guess at why GCC accepts sizeof f is that it is part of allowing address arithmetic on pointers generally, even void * and pointers to functions, to support code used in operating systems, debuggers, program loaders, and similar software that operates on functions and arbitrary memory. Making sizeof f be 1 is a part of designing GCC’s arithmetic on function pointers to operate in units of bytes. (This is an unnecessary feature of GCC because we could support arithmetic on pointers to functions and void * by requiring they be first converted to a pointer to a character type. However, I suppose it was done to support code practices at the time it was done and is now legacy.)

    At a syntax level, &f differs from f in being longer, so it could run afoul of environmental limits of the compiler. For example, C 2018 5.2.4.1 1 requires a compiler support 4095 characters in a logical source line (in at least one program), so changing f to &f could push the line over the length that a compiler supports.

    With *fp versus fp, similar reasoning applies: *fp is converted to the equivalent of fp in many situations, so semantic differences arise only when it is not. So sizeof *fp and sizeof fp differ as discussed above. However, for &, &*fp is the address of the function, whereas &fp is the address of the pointer. (This supposes that fp is an object, not some other expression that is not an lvalue. If it is not an object, it raises the same issues as discussed for & above.)

    And *fp versus fp has the same environmental limit consideration as &f versus f.