34

I always understood that in C, func and &func were equivalent. I assume they should both be of type pointer, which is 8 bytes on my Win64 system. However, I just tried this:

#include <stdio.h>

int func(int x, int y)
{
    printf("hello\n");
}

int main()
{
    printf("%d, %d\n", sizeof(&func), sizeof(func));
    return 0;
}

And expecting to get the output 8, 8 was surprised to get 8, 1 instead.

Why is this? What type exactly is func? It seems to be of type char or some equivalent.
What is going on here?

I compiled this with gcc -std=c99 if it makes a difference.

yulian
  • 1,601
  • 3
  • 21
  • 49
Baruch
  • 20,590
  • 28
  • 126
  • 201
  • 7
    `func` is of type "function" and doesn't really have a defined size. `&func` is of type "pointer to a function", so it's size is the size of a pointer. – lurker Sep 15 '13 at 20:04
  • 1
    `gcc -Wall -Wextra -pedantic -std=c99` is your friend – Mat Sep 15 '13 at 20:05
  • @Mat You forgot `-Werror -pedantic-errors`. –  Sep 15 '13 at 20:08
  • related: http://stackoverflow.com/questions/18626080/are-there-any-differences-between-these-two-higher-order-function-definitions/18626123#18626123 – zneak Sep 15 '13 at 20:09
  • 1
    @mbratch: Its important to note that function pointers don't necessarily have the same size as object pointers: http://c-faq.com/ptrs/generic.html – hugomg Sep 16 '13 at 01:11

4 Answers4

38

What type is a function name in C?

A function name or function designator has a function type. When it is used in an expression, except when it is the operand of sizeof or & operator, it is converted from type "function returning type" to type "pointer to a function returning type". (This is specified in C99, 6.3.2.1p4).

Now

sizeof(func)

is not valid C as sizeof is not allowed with an operand of function type. This is specified in the constraints of the sizeof operator:

(C99, 6.5.3.4p1 Constraints) "The sizeof operator shall not be applied to an expression that has function type or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member."

But

sizeof(func)

is allowed in GNU C.

There is a GNU extension in GNU C that allows it and in GNU C sizeof with an operand of function type yields 1:

6.23 Arithmetic on void- and Function-Pointers

[...] sizeof is also allowed on void and on function types, and returns 1.

http://gcc.gnu.org/onlinedocs/gcc/Pointer-Arith.html

Cornstalks
  • 37,137
  • 18
  • 79
  • 144
ouah
  • 142,963
  • 15
  • 272
  • 331
  • 6
    Is there any rationale on why they chose to implement this *and* return 1? It seems to me like they created an unnecessary source of confusion. – Theodoros Chatzigiannakis Sep 15 '13 at 20:06
  • 8
    @TheodorosChatzigiannakis Because the GNU people are themselves confused. Just look at the C coding style they suggest to follow. Aaaaargh! –  Sep 15 '13 at 20:07
  • 5
    @TheodorosChatzigiannakis Take a look at the link, this is done in order to allow pointer arithmetic with `void *` and pointer to functions in GNU C. – ouah Sep 15 '13 at 20:09
  • 1
    @ouah: Pointer arithmetic with `void*` can arguably be useful because you avoid all the casts to `char*` just to advance the pointer when dealing with raw memory. But arithmetic with pointer functions is just evil. – rodrigo Sep 16 '13 at 00:39
  • @TheodorosChatzigiannakis Just speculating, it may be gcc backwards compatibility thing, which they have chosen not to break. Gcc predates C89, after all, so if this behavior was in first gcc, it did not break any standard yet :-) – hyde Sep 16 '13 at 03:51
13

Given:

int func(int x, int y) { /* ... */ }

the expression func is of function type. Specifically, it's of type int(int, int), which is C's syntax for the type "function with two int parameters returning int. (You won't often see that particular syntax, since it's not common to refer to function types directly.)

In most contexts, an expression of function type is implicitly converted to a pointer to the function; in this case, the pointer is of type int(*)(int, int).

The contexts in which this implicit conversion does not occur are:

  1. When the expression is the operand of unary &; in that case, &func yields the address of the function (just as func by itself usually does); and

  2. When the expression is the operand of sizeof. Without this exception, sizeof func would yield the size of a function pointer. Instead, it's a constraint violation, requiring a diagnostic from the compiler.

(A side note: This conversion does happen when the function name is used in a function call. The () "operator" (the standard doesn't call it that) requires a prefix of pointer-to-function type.)

gcc happens to have a non-standard extension; it permits pointer arithmetic on function pointers and on type void*, acting like pointer arithmetic on char* pointers (i.e., it operates in units of bytes). Unfortunately, IMHO, gcc did this via a kludge, setting the size of function types and of type void to 1. That's why you get sizeof func == 1; if you enable one of the standard conforming modes (e.g., gcc -std=c99 -pedantic), you'll get a warning.

Incidentally, don't use %d to print the result of sizeof. sizeof yields a result of type size_t. If your implementation supports it (C99 or later), use %zu; if not, you need to use a cast to explicitly convert the size_t value to something that you can print. For example:

printf("%lu\n", (unsigned long)sizeof &func);
Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 4
    Where you thinking `printf("%lu\n", (unsigned long) sizeof &func)` in the last paragraph? – chux - Reinstate Monica Sep 15 '13 at 20:17
  • I wonder what the use of pointer arithmetic with a function pointer is supposed to mean. With a `void *` I can understand it, but what is `(funcptr+1)` supposed to mean? – glglgl Sep 15 '13 at 22:02
  • @glglgl: I've never had any use for it, but perhaps there are some low-level uses for computing the address N bytes before or after the start of a function (if you know how the machine code is laid out), or computing the difference between two function pointers. – Keith Thompson Sep 15 '13 at 22:06
  • @KeithThompson mmm... very far away from any "defined-ness"... let's hope the GCC guys know what they do... – glglgl Sep 15 '13 at 22:07
  • (funcptr +1) probably means "Segmentation Fault" the moment you dereference. It looks pretty fishy to me. – ncmathsadist Sep 16 '13 at 01:32
  • @ncmathsadist: Sure, I don't think 1 is a sensible value to add to a function pointer -- but N might be for some highly system-specific value of N. The fact that gcc does its implementation-specific function pointer arithmetic in terms of bytes makes it easier to work with. – Keith Thompson Sep 16 '13 at 02:09
  • 3
    @glglgl I think this is more of a case, that gcc people hope the C *programmer* knows what he's doing. Which, I suppose, is somewhat in the spirit of C. – hyde Sep 16 '13 at 04:03
5

What type is a function name in C?

It's of a function type.

I always understood that in C, func and &func were equivalent

Well, they are not "equivalent". A function does, however, decay into a pointer-to-function.

I assume they should both be of type pointer

That's an incorrect assumption.

And expecting to get the output 8, 8 was surprised to get 8, 1 instead. Why is this?

Because 1. it's UB if it compiles, 2. it shouldn't even compile in first place, and as such, your program is free to do anything.

  • 1
    @Mat Since it's a constraint violation, if it compiles, then it invokes UB. For one thing, it shouldn't compile, but here it apparently does. It does **not** mean that it **must** be an extension/documented. –  Sep 15 '13 at 20:16
  • 1
    @Mat And a program produced by a non-conforming implementation... invokes UB! Or doesn't it? (Oh, and strictly speaking, GCC isn't conforming, unless you do `-Werror -Wall -Wextra -pedantic -pedantic-errors -ansi`, perhaps.) –  Sep 15 '13 at 20:19
  • Ok, sorry for the noise. I was thinking a conforming impl. should not compile, that's not the case, it should diagnose. I have no idea what you can do with the resulting binary if a conforming impl. does produce one after telling you it was rubbish :-) – Mat Sep 15 '13 at 20:29
  • 3
    @Mat Probably it can be used to confuse two programmers debating about its behavior ;-) –  Sep 15 '13 at 20:33
  • @Mat I think warning in this case means, "*this* binary has a gcc-specific well-defined behavior, but if you compile it with a different standards conforming compiler, *that* binary might erase all your files". Which is useful if you are fixing old code to be standards compliant. – hyde Sep 16 '13 at 04:12
1

Names do not have a type in C. Some kinds of names denote entities that have type, such as typedef names, objects or functions. Other kinds of names denote entities that do not have a type, such as preprocessor symbols or goto labels. Yet other kind of names simply denote types themselves, namely typedef names.

The name of a function denotes an entity that has function type. That function type includes the return type, and (possibly incomplete) information about the parameters.

When functions are used as values, they are always manipulated as pointer-to-function types. A function as such cannot be passed around in a portable C program, but a pointer to a function can be.

Conceptually, even in a direct call like foo(), what happens is that foo, an expression denoting a function, upon evaluation is implicitly converted to a pointer-to-function value. The () function call postfix operator then invokes the function by means of this pointer.

There is a rule that an expression that has function type produces a pointer value, except if that expression is the operand of the & (address of) or of the sizeof operator. func and &func are only equivalent in the sense that they produce the same value. func produces a pointer value implicitly. &func suppresses the implicit generation of a pointer (func is the operand of & and so the conversion is suppressed), but then & takes the address.

So you can see that sizeof &func and sizeof func are different. The former takes the size of a pointer, and the latter tries to take the size of a function.

Taking the size of a function is a constraint violation in C: it requires a diagnostic from an implementation that conforms to the standard. If the program still translates and a value of 1 is produced when it is run, that is "bonus" behavior specific to your language implementation. It is not in the standard language.

Kaz
  • 55,781
  • 9
  • 100
  • 149