Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange compiler warning C: warning: ‘struct’ declared inside parameter list

Tags:

I just found a quirk in C that I find really confusing. In C it's possible to use a pointer to a struct before it has been declared. This is a very useful feature that makes sense because the declaration is irrelevant when you're just dealing with a pointer to it. I just found one corner case where this is (surprisingly) not true, though, and I can't really explain why. To me it looks like a mistake in the language design.

Take this code:

#include <stdio.h>

#include <stdlib.h>


typedef void (*a)(struct lol* etc);

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}

Gives:

foo.c:6:26: warning: ‘struct lol’ declared inside parameter list [enabled by default]
foo.c:6:26: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default]
foo.c:8:16: warning: ‘struct lol’ declared inside parameter list [enabled by default]

To remove this problem we can simply do this:

#include <stdio.h>

#include <stdlib.h>

struct lol* wut;

typedef void (*a)(struct lol* etc);

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}

The unexplainable problem is now gone for an unexplainable reason. Why?

Note that this question is about the behavior of language C (or possible the compiler behavior of gcc and clang) and not the specific example I pasted.

EDIT:

I won't accept "the order of declaration is important" as an answer unless you also explain why C would warn about using a struct pointer for the first time in a function argument list but allow it in any other context. Why would that possibly be a problem?

like image 564
Hannes Landeholm Avatar asked May 30 '13 08:05

Hannes Landeholm


1 Answers

To understand why the compiler complains, you need to know two things about C "struct"s:

  • they are created (as a declared, but not yet defined, type) as soon as you name them, so the very first occurrence of struct lol creates a declaration
  • they obey the same "declaration scope" rules as ordinary variables

(struct lol { declares and then begins defining the structure, it's struct lol; or struct lol * or something else that does not have the open-brace that stops after the "declare" step.)

A struct type that is declared but not yet defined is an instance of what C calls an "incomplete type". You are allowed to use pointers to incomplete types, as long as you do not attempt to follow the pointer:

struct lol *global_p;
void f(void) {
    use0(global_p);     /* this is OK */
    use1(*global_p);       /* this is an error */
    use2(global_p->field); /* and so is this */
}

You have to complete the type in order to "follow the pointer", in other words.

In any case, though, consider function declarations with ordinary int parameters:

int imin2(int a, int b); /* returns a or b, whichever is smaller */
int isum2(int a, int b); /* returns a + b */

Variables named a and b here are declared inside the parentheses, but those declarations need to get out of the way so that the the next function declaration does not complain about them being re-declared.

The same thing happens with struct tag-names:

void gronk(struct sttag *p);

The struct sttag declares a structure, and then the declaration is swept away, just like the ones for a and b. But that creates a big problem: the tag is gone and now you can't name the structure type ever again! If you write:

struct sttag { int field1; char *field2; };

that defines a new and different struct sttag, just like:

void somefunc(int x) { int y; ... }
int x, y;

defines a new and different x and y at the file-level scope, different from the ones in somefunc.

Fortunately, if you declare (or even define) the struct before you write the function declaration, the prototype-level declaration "refers back" to the outer-scope declaration:

struct sttag;
void gronk(struct sttag *p);

Now both struct sttags are "the same" struct sttag, so when you complete struct sttag later, you're completing the one inside the prototype for gronk too.


Re the question edit: it would certainly have been possible to define the action of struct, union, and enum tags differently, making them "bubble out" of prototypes to their enclosing scopes. That would make the issue go away. But it wasn't defined that way. Since it was the ANSI C89 committee that invented (or stole, really, from then-C++) prototypes, you can blame it on them. :-)

like image 141
torek Avatar answered Sep 20 '22 03:09

torek