3

I'm trying to pick up blocks in objective-c and am learning about pointers to functions so this might be a really obvious question. I compiled the following in Xcode 5 and have the following:

void hearThis(char * (*pFunc)(char *myString)){
 /*
  why does it allow me to do this? when signature for 
   helpMe doesn't take an argument
  */
    char *str=(*pFunc)("what I want as arg"); 
    printf("you are going to hear this: %s\n", str);
}


char * helpMe(){
  return "this is from helpMe";
}

int main(){
    char * (*fNewFunc)();
    fNewFunc=&helpMe;
    // we are passing a pointer to a function
    hearThis(fNewFunc);

which outputs:

you are going to hear this: this is from helpMe

Why does it allow me to even compile when it is passing an argument to a function that doesn't take one OR not crash when run?

timpone
  • 19,235
  • 36
  • 121
  • 211
  • 7
    In C, declaring a function with no parameters _must_ be declared with `void` parameters. e.g., `char * (*fNewFunc)(void);` If declared without parameters, you're declaring _a_ function with _unspecified parameters_. It's not the same. – Jeff Mercado Dec 12 '13 at 18:54
  • See also: [C void arguments](http://stackoverflow.com/q/693788/390278) – Jeff Mercado Dec 12 '13 at 18:58
  • thx Jeff, I forgot about that – timpone Dec 12 '13 at 18:59
  • @JeffMercado that's valid for ANSI C, in C99 `char * helpMe()` is the same as `char *helpMe(void)` – Marco Dec 12 '13 at 19:02
  • 1
    @Marco where does the standard state that? – effeffe Dec 12 '13 at 19:05
  • 1
    @Marco is `gcc --std=c11` in error then? Calling `char*helpMe()` with `helpMe("test")` compiles silently, with `char*helpMe(void)` it warns. – jthill Dec 12 '13 at 19:11
  • @Marco: It is _deprecated_ to use the parameterless version in C99, it's still not the same. – Jeff Mercado Dec 12 '13 at 19:11
  • @JeffMercado have you considered giving your first comment as an answer? As it is I can only upvote your comment, even though the question is just an oversight it seems to me it's worth an answer on SO. – jthill Dec 12 '13 at 19:18
  • what? in C99 you should use int func(); instead of int func(void); =/ – Marco Dec 12 '13 at 19:19
  • 1
    @Marco No, they are not the same, and you should always specify a parameter list. – effeffe Dec 12 '13 at 19:21
  • Hm.. I will delete my answer until I read a bit about that. I was sure that it was the other way around. – Marco Dec 12 '13 at 19:21
  • @jthill: Usually when I see a C question involving a function with an empty parameter list, I can immediately tell that the problem is going to be due to some misunderstanding on what it really means. And when I post answers, I generally try to explain everything, something I just didn't have the time for right now. I might have if I had more time but it's not a big deal for me if someone else does. – Jeff Mercado Dec 12 '13 at 19:42
  • thx for your help Jeff. I haven't touched real C in many years and don't think every pointers to functions. Thx again – timpone Dec 12 '13 at 19:45
  • @effeffe Apparently `char *helpMe() { return "this is from helpMe"; }` is the same as `char *helpMe(void) { return "this is from helpMe"; }` in C99 and later. – Marco Dec 12 '13 at 19:59
  • @Marco Try to call that function with arguments. – effeffe Dec 12 '13 at 20:41
  • 1
    @effeffe I'm refering to what the standard (for c99 and c11) says. Read: §6.7.5.3.14 `An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters.` – Marco Dec 12 '13 at 20:51
  • @Marco Wait, sorry for the confusion, your last example is different from the first one you made: `void foo();` and `void foo(void);` are different because they're not definitions, just declarations; `void foo() { }` and `void foo(void) { }` are also definitions and therefore they are the same. – effeffe Dec 12 '13 at 20:55
  • @effeffe Yeah, but the last example is the same case that this question.. So the problem is not with helpMe but with the function that casts. It IS undefined behaviour after all. On the other hand, why can you call foo with arguments ? compiler doesn't follow the standard? – Marco Dec 12 '13 at 20:57
  • @Marco The program does not contain any cast, which line do you think it's invoking UB exactly? – effeffe Dec 12 '13 at 21:04
  • Look at my answer @effeffe. The undefined behaviour happens when calling `pFunc` from `hearThis`. The function types are incompatible (that says gcc), causing an undefined behaviour. – Marco Dec 12 '13 at 21:23
  • @Marco Oh, now I see what you mean: since you were talkin' about "casts" (and btw these are just implicit conversions, not casts) I didn't understand you were referring to *that* function call, my bad. Good point, I agree with you on the UB call, +1 to your answer. – effeffe Dec 12 '13 at 21:36
  • @jthill I think that gcc and clang are both wrong :( they should display a warning. Maybe it is woth reporting, the thing is that using an empty parameter list is deprecated, so maybe they won't even bother. BTW, I have edited my answer after reading the standard. – Marco Dec 12 '13 at 22:05
  • @Marco It's possible to get some warnings about missing parameter list in function declaration, with appropriate warning settings. gcc can do it. – effeffe Dec 14 '13 at 21:58
  • @effeffe Afaik, those warnings are for function prototypes, but old-style function declarations which aren't prototypes don't get those :( There are some warnings on clang, but on gcc I couldn't find a way to get a warning. As it has been said before, the best is to avoid old-style function declarations :P (I still think it looks better :( ) – Marco Dec 15 '13 at 01:42
  • @Marco `-Wstrict-prototypes`, for example, gives a warning for the OP's code, wich goes away if you explicitly add `void` as parameter list. There are also more pedant warning options. But yeah, it's best to avoid this declarations at all. – effeffe Dec 15 '13 at 11:03
  • @effeffe the thing with strict-prototypes is that it warns you if you don't use function prototypes (i.e: it warns you to avoid using old-style declarations). So, the warning is for a different reason. The definition without the void parameter is totally valid and one would think that the compiler should throw a warning because that's undefined behaviour :P – Marco Dec 15 '13 at 21:24
  • @Marco oh well, that kind of UB is impossible to catch for a compiler, sure. – effeffe Dec 16 '13 at 01:18

2 Answers2

3

C syntax for a function declaration taking no arguments is f(void). Your fNewFunc is to a function taking unspecified arguments. Replace its declaration with

char * (*fNewFunc)(void);

and C can then at least warn you about the mismatch. C++ will reject it.

edit: as chux points out in comments, the char *helpMe() definition, since it is a definition, does declare that helpMe takes no parameters, so there's no strict need to change it.

jthill
  • 55,082
  • 5
  • 77
  • 137
  • This appears _almost_ correct. C11dr §6.7.6.3 14 "... An **empty list** in a function declarator that is **part of a definition** of that function specifies that the function has **no parameters**. The **empty list** in a function declarator that is **not part of a definition** of that function **specifies that no information** about the number or types of the parameters is supplied." (My emphasis.) In OP's code, `helpMe()` is the same as `helpMe(void)` as its part of the definition and not a bare declaration. – chux - Reinstate Monica Dec 12 '13 at 21:47
  • @chux my mistake, though fortunately for OP it makes no difference here (the assignment being filtered through the no-parameters `fNewFunc` declaration. Thanks for the correction, editing it in now. – jthill Dec 12 '13 at 22:19
2

Ok, I have checked the c99 standard and can say that this is undefined behaviour.


TL;DR: It is ok to cast/convert one function pointer to another type, but if you call that function, the function types must be compatible, otherwise, you get undefined behaviour. You don't get a warning because in each step you are converting between compatible function types, but in the end, helpMe isn't compatible with pFunc.


First of all:

char *helpMe(){
  return "this is from helpMe";
}

Is valid and in C99 is the same as:

char *helpMe(void){
  return "this is from helpMe";
}

From §6.7.5.3 (emphasis mine):

An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

I don't know how this behaves in C89, I think that maybe, in C89 that is valid.

On the other hand, we have:

char * (*fNewFunc)() = helpMe;

This cast is valid, because you can cast a function pointer to a function of another type and go back without problem. The compiler won't throw a warning because even their types are compatible! (void against unespecified).

Section §6.3.2.3, paragraph 8 reads (again, emphasis mine):

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

So, when you pass the pointer to hearThis it is casted again, from char *(*)(); to char *(*)(char *); which, again, they are compatible for the compiler (thus, not throwing a warning).

BUT the standard says if the function type you are calling is incompatible with the function type pointed to, then, the behaviour is undefined.

Te question now is, are they compatible?

The answer is NO. if you change the type of fNewFunc() to char *(*fNewFunc)(void); you will get a warning:

p.c: In function ‘main’:
p.c:12:5: warning: passing argument 1 of ‘hearThis’ from incompatible pointer type [enabled by default]
p.c:6:6: note: expected ‘char * (*)(char *)’ but argument is of type ‘char * (*)(void)’

In the standard there also says when two functions types are compatible. It is in §6.7.5.3.15. It's a bit more complicated than this :P

Why does it work?

Probably it works because the computer doesn't care. It just pushes or passes the parameters as registers, and helpMe just ignores them. But you should know that it is undefined behaviour.

Marco
  • 2,796
  • 19
  • 24