2

I know that declaring a function (or function pointer) with no parameter list (and without specifying void in the parameter list) that means that the function (or function pointer) has an unknown number of arguments.

I wrote some test scripts to check this behavior out:

int my_func_1() {
    return(0);
}
int my_func_2(int x) {
    return(x);
}
int my_func_3(char x, ...) {
    va_list va;
    va_start(va, x);
    return(va_arg(va, int));
}


int (*fp)();

fp = my_func_1;
printf("%d\n", fp());

fp = my_func_2;
printf("%d\n", fp(33));

fp = my_func_3;
printf("%d\n", fp(33, 44));

Which I've compiled on a 64 bit machine under linux as such:

gcc test.c -Wextra -pedantic -std=c1x -O3 -o test

The output is correct:

0
33
44

But I get this warning: assignment from incompatible pointer type. How come this warning only shows up when using the vararg ellipsis?

Since the pointer type is considered incomplete assigning it a complete type should not constitute an "assignment from incompatible pointer type". Moreover, assigning complete types works without any warnings as long as the vararg ellipsis is not present

This other question asks nearly the same thing. The answers are all in the line of "it doesn't work that way" with no clear reference to the standard as to why it wouldn't work that way.


  • Why does the compiler generate the warning?
  • Is this behavior (warning aside) reliable?
  • Is this behavior compiler specific (reading the other question it appears that mingw x86_64 didn't support it in 2011)?
  • Is this behavior platform specific?
Community
  • 1
  • 1
Mihai Stancu
  • 15,848
  • 2
  • 33
  • 51
  • Why bother exploring such things? No sensible C code in this century uses parameterless prototypes. *The answers are all in the line of "it doesn't work that way"* -- no, the accepted answer says it's undefined behavior. – Jim Balter Dec 19 '13 at 00:45
  • I'm just curious; I have a decent understanding of C internals and when I see an opportunity to test my understanding/assumptions etc. I go for it. – Mihai Stancu Dec 19 '13 at 00:57
  • 2
    Your question goes beyond that. e.g., you ask if the behavior is reliable ... well, no UB is reliable. – Jim Balter Dec 19 '13 at 01:00
  • I ask if it is reliable because a) my expectation of how the UB would work was met; b) the parameterless prototypes had a design goal; they may be obsolescent (as I've read somewhere) but I am nevertheless curious what the design goal was. – Mihai Stancu Dec 19 '13 at 01:04
  • If the design goal was just for forward declarations which would then be superseded/completed by the definition -- to avoid having to manually maintain two prototype lists -- that seems a bit useless (to me). – Mihai Stancu Dec 19 '13 at 01:06
  • Also, the UB at hand here is calling a function through an incomplete (but compatible) pointer type. The warning is generated by an operation which should be perfectly legal (because of the compatibility). – Mihai Stancu Dec 19 '13 at 01:18
  • UB is *undefined* behavior. Since it's undefined, it can't be reliable; that it works in some implementation at some moment isn't relevant. "what the design goal was" -- there wasn't one; parameterless forward declarations long preceded the addition of prototypes to C. They are indeed obsolescent ... don't use them. – Jim Balter Dec 19 '13 at 01:37
  • I'm not trying to mince words here. When I asked the question I didn't know the UB is UB, hence the question. Between then an now I've explained my intent -- curiosity, implementation details, *why*. Saying "it doesn't work anyway, don't use it" will not change the fact that I am curious about the internals/details. – Mihai Stancu Dec 19 '13 at 01:42
  • I don't want to waste your time, the question is purely theoretical and it proposes a toy exercise. It is (and has been since the beginning) ultimately futile except for the lessons learnt about compiler internals. – Mihai Stancu Dec 19 '13 at 01:44
  • *Since the pointer type is considered incomplete* -- no, function pointer types are never incomplete (see the first paragraph of 6.2.5). *should not constitute an "assignment from incompatible pointer type"* -- I don't know why you think so. The only pointer type compatible with a varargs function pointer is a varargs function pointer type. – Jim Balter Dec 19 '13 at 01:47
  • * "it doesn't work anyway, don't use it"* -- That's not all I said; I answered your question about design goals. *will not change the fact that I am curious* -- of course not, but it pertains to whether this is an appropriate question for SO, which is intended for specific programming problems. – Jim Balter Dec 19 '13 at 01:51
  • It is a valid programming specific SO question (albeit obscure) and it has a clear cut answer -- which we could for the most part summarize from our discussion here. – Mihai Stancu Dec 19 '13 at 01:55
  • On the "incomplete type" issue; I was not referring to traditional incomplete types such as opaque pointers. What I meant was that semantically a function with no parameters and no `void` in the parameter list is said to "take an unknown number of parameters". That means that there is a clearly defined meaning of what you can/can't do with such a function. – Mihai Stancu Dec 19 '13 at 01:57
  • Parameterless functions take un unknown number of parameters. As long as parameterless function pointers are legal C code, and as long as they are not interpreted as meaning no parameters, then function pointers should be able to take un unknown number of parameters too. – Mihai Stancu Dec 19 '13 at 01:59
  • *On the "incomplete type" issue; I was not referring to* -- The standard defines what an incomplete type is. That definition is the only thing relevant to what is "considered" or what "should not constitute" when talking about what the behavior of an implementation should be. *That means that there is a clearly defined meaning* -- no, the only clearly defined meanings are those specified in the standard. *Parameterless functions take un unknown number of parameters.* -- No, parameterless functions take no parameters. Parameterless function **declarators** aren't functions. – Jim Balter Dec 19 '13 at 02:11
  • *then function pointers should be able to take un unknown number of parameters too* -- this is a *moral* declaration of how you think the world should be ... it has nothing to do with what C implementations should do. The standard says that it's undefined behavior, so C implementations are allowed to do anything. I give one pragmatic reason for this latitude in my answer. – Jim Balter Dec 19 '13 at 02:12
  • Last question: functions with empty declarators are supposed to have a subsequent declarator that supplies the param-list or void. If -- *Parameterless function declarators aren't functions* -- then it should be a compile time error the same as trying to get `sizeof(my_opaque_pointer*)`. Shouldn't it? – Mihai Stancu Dec 19 '13 at 02:23
  • *functions with empty declarators are supposed to have a subsequent declarator that supplies the param-list or void.* -- no, functions with empty declarators take no parameters ... how would the function body know the names of the parameters? Function declarators with empty lists are different -- they are forward decls that provide no information about the function's parameters -- and they are obsolescent. *same as trying to get sizeof ...* -- no, all function pointers have the same size (and you can't get the size of the function itself). – Jim Balter Dec 19 '13 at 02:31
  • @JimBalter: If function X takes two function pointers Y and Z, and passes Z to Y but doesn't invoke it directly, is there any way to write the declaration for X to be agnostic with respect to the signature of function Z? If the return type of `Z` is known, I would think something like `void X(int (*Y)(int(*)()), int (*Z)())` should work, but can't think how code could be written without using the deprecated `()` form. – supercat Nov 27 '16 at 22:30
  • You can pass around void pointers and cast them when you actually use them. – Mihai Stancu Nov 27 '16 at 23:05
  • That was a bit harsh and uncalled for. – Mihai Stancu Nov 28 '16 at 14:59
  • But being that this entire discussion was on fringe situation answered withing the realm of possibility not of good practice. FYI I am not a C developer, and I had no goal/target on any progress in "realworld C development skills" -- I just played with and tried to understand it, understanding fringe cases Is a good way to understand the bounds you should/could work in. – Mihai Stancu Nov 28 '16 at 15:15
  • BTW i remember `dlsym` returns void pointers making this literally necessary... – Mihai Stancu Nov 28 '16 at 15:22
  • @supercat the best approach you can alter (or wrap) the functions to return a (tagged?) union of the two types. This is the most coherent approach I can think of to handle your situation -- it keeps type safety for both your return types and your function signatures. Another approach is to alter the functions to return void pointers (so that the signature becomes the same) but this would entail casting the returned data from void pointers. – Mihai Stancu Nov 28 '16 at 15:36
  • @MihaiStancu: Function X shouldn't need to know or care about the number or types of arguments Y is going to pass to Z, since X has no involvement with those function calls. While it might be possible to require that Y must always put everything it wants to pass to Z into a structure and pass its address as a void*, that would likely be bad for performance. Allowing Y to have a signature that includes "pointer to some unknown kind of function pointer", and allowing Z to be "pointer to unknown function" would allow the logical semantics. Passing incompatible functions for Y and Z... – supercat Nov 28 '16 at 17:06
  • ...would of course be disastrous, but an easy remedy is simply "don't do that". – supercat Nov 28 '16 at 17:07
  • "I didn't advise it as a good thing" -- I didn't say anything about it being a good or bad thing, I said "you **can't** cast **function** pointers to or from `void *` -- it's undefined behavior. – Jim Balter Dec 06 '16 at 22:22
  • When using `dlsym` to get a reference to an external symbol from a shared object it will return `void*` and you will cast it to an object or a function. Ex.: `*(void **) (&f) = dlsym(handle, "foo");` Standard or not, defined behavior or not, that's what you do. – Mihai Stancu Dec 06 '16 at 22:55
  • @jimbalter the C11 standard explicitly defines this behavior in [annex J.5.7.](http://port70.net/~nsz/c/c11/n1570.html#J.5.7). – Mihai Stancu Dec 06 '16 at 23:42
  • Um, wrong. Annex J is "informative", not "normative", so it does not "define" anything. J.5, specifically, describes *extensions* ... these are not part of the standard. Not only can't conforming programs count on these being supported, but an implementation may risk becoming nonconforming by implementing them (though that doesn't apply to function casts). – Jim Balter Dec 07 '16 at 04:53
  • "When using dlsym" -- dlsym is not part of the C standard. When you wrote "You can pass around void pointers and cast them when you actually use them", if you meant "on POSIX systems", you should have said so and then we could have skipped this whole conversation. But the question isn't tagged POSIX and supercat's question was not POSIX-specific. The discussion here is about the C programming language, not POSIX. – Jim Balter Dec 07 '16 at 05:12
  • @supercat "Allowing Y to have a signature that includes "pointer to some unknown kind of function pointer", and allowing Z to be "pointer to unknown function" would allow the logical semantics." -- So petition the C standards committee to add this feature to the language. When the committee found themselves writing in the standard that function pointers can't be cast to `void*`, they should have realized that they needed to add a corresponding funcptr type to the language. – Jim Balter Dec 07 '16 at 05:16
  • @jimbalter but as long as J.5.6.7. leaves the door open for implementations to define the behavior then is is still UB? – Mihai Stancu Dec 07 '16 at 07:54
  • Yes, of course it is. – Jim Balter Dec 07 '16 at 10:18
  • @JimBalter: The syntax `int (*foo)();` already exists, and declares a pointer whose return type is unknown, but whose arguments can be anything, so there's no need to add anything to the Standard. My point was that such syntax makes it possible to express semantics which cannot be expressed any other way, and should thus be *retained*. – supercat Dec 07 '16 at 15:22
  • @jimbalter My original Q was about compiler & platform specific behaviors. You brought the standards to the table. Omitting POSIX was in your court. – Mihai Stancu Dec 07 '16 at 17:57
  • @jimbalter if I may add some constructive criticism we have discussed a great deal based on my poor choice for words (using common words which in jargon have very particular nuances). Perhaps you could think of how to differentiate between jargon-aware/unaware people before answering a Q or comment in the strictest sense of the jargon. This is necessary because only you can be aware of the problem, the "junior" would be oblivious. Disclaimer: correct jargon has a tremendous effect on the quality and brevity of a conversation but it absolutely requires the jargon to be shared. – Mihai Stancu Dec 07 '16 at 18:13
  • @supercat `int (*foo)()` specifies a return type, which is just as irrelevant to X as are the parameter types in your example. What you want is a generic "pointer to function" type that is guaranteed to be the right size and so can be copied but is otherwise unusable until cast to a specific function type. – Jim Balter Dec 08 '16 at 02:49
  • @MihaiStancu Your original Q is about C11, and that's how it's tagged. It asked *whether* certain behaviors are compiler- and platform-specific, which is implicitly a question about the standard. The C standard is always on the table when C is discussed ... see thousands of SO responses to C questions referencing the standard. Again, supercat's question (to me) on Nov 27 was not platform-specific, and so, again, your Nov 27 comment, which did not say "on POSIX", was wrong. Now enough of this disingenuous chitchat, which SO is now warning us about. – Jim Balter Dec 08 '16 at 02:52

1 Answers1

1

See 6.7.5.3 of the C Standard:

Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions.

This says that the assignment of the varargs function to fp has incompatible types.

In addition, as I noted in my comments above, the program has undefined behavior which by definition is unreliable ... it may work in one version of one implementation on one platform but fail in other versions, implementations, and platforms ... it might even work one day but not the next, or in one part of a program but not another. Pragmatically, in some implementations the calling sequences of varargs and non-varargs functions are different, and your program is likely to crash when run on such an implementation.

Jim Balter
  • 16,163
  • 3
  • 43
  • 66
  • A-ha! So the spec clearly states that `empty lists` are incompatible with `ellipsis terminators`. – Mihai Stancu Dec 19 '13 at 02:02
  • @MihaiStancu Note that I added some text to make my answer more complete since you accepted it. – Jim Balter Dec 19 '13 at 02:04
  • I asked for info about reliability vs. portability because there are many compiler extensions/features that have worked in one compiler long before they were added to the standard, they eventually got supported by all compilers and years later ended up in the spec (flexible array fields, anonymous structs/unions, anonymous fields etc.). – Mihai Stancu Dec 19 '13 at 02:07
  • @MihaiStancu Features that are completely missing from the standard (that in fact require diagnostics from conforming implementations) are quite different from constructs that are explicitly stated to have undefined behavior -- those statements usually reflect implementations known to the authors of the standard, and that behave differently ... such as here. – Jim Balter Dec 19 '13 at 02:17
  • Thank you very much for your time and effort. – Mihai Stancu Dec 19 '13 at 02:26