Hannes Landeholm
Hannes Landeholm

Reputation: 1629

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

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?

Upvotes: 27

Views: 59431

Answers (6)

Y.K.
Y.K.

Reputation: 1

In addition to other answers, I would like to post a code example, which makes the problem more obvious. Please consider the following:

int testFunc(struct SomeStruct {int a; int b;} param1) // gcc: warning ...  
{
  return param1.a + param1.b;
}

int main(void)
{
    struct SomeStruct params; // gcc: error: storage size of 'params' isn't known
    params.a = 25;
    params.b = 15;
    return testFunc(params);
}

As you can see, the function declaration of testFunc is considered a valid C code (tested with GCC 12, Clang 15 and MSVC 19.32). However, you can't really use it because SomeStruct is only valid within the scope of the function, which is what compiler warns you about.

I've stumbled on this myself when working on my own C parser as the allowance of such syntax makes parser development easier as you can reuse the same implementation to parse struct declaration inside function parameter lists. It is surprising, however, that such bizarre syntax is still considered valid nowadays (as of C17) and instead of error you just get a warning.

Upvotes: 0

smwikipedia
smwikipedia

Reputation: 64173

I see this same warning before. My fix is to include the proper header file which contains the definition of the struct.

Upvotes: 0

KANJICODER
KANJICODER

Reputation: 3885

Lookout for typos and DOUBLE CHECK your line numbers! I concat all my source files before compiling, so the line numbers from the source I am working in are meaningless. I have to take extra time to open up the concatted payload and examine it. So usually I don't and I just assume I know what line I am looking at from the console output message.

Example: GCC Says:

EXAMPLE.C11:27:1: error: 'struct THIS_STRUCT_IS_OK' declared inside parameter list [-Werror] ){

#include <stdio.h> //:for: printf(...)

struct THIS_STRUCT_IS_OKAY{
    int whatever;
};


int LookingAtThisFunction(

    struct THIS_STRUCT_IS_OKAY* arg

){ 
   //:    (Because you are not checking line numbers, you    )
   //:    (assume you are looking here. But you are not.     )
   //:    (Maybe you are concatenating all of your source    )  
   //:    (files together before compiling and line numbers  )
   //:    (don't correspond to the original source and you   )
   //:    (didn't examine your concatted source code payload?)

    return( arg -> whatever );

}

//:You are not looking here because this is later in the
//:file, so the compiler would be complaining about the
//:FIRST usage of the struct, not the second one, you assume.
//:And you would be correct, if there wasn't a typo.
void WhereYouAreNotLooking(
    struct THIS_STRUCT_IS_OK* arg
){ 

    LookingAtThisFunction( arg );

}


int main( void ){

}

In summary: If you know what the error message means. And you swear to god the compiler is broken because you already checked that... 1. Look for typos. 2. Make sure you are really looking at the correct line number.

I get that this is kinda stupid. But it had been scratching my head for half an hour. So hopefully it helps someone who's already looked at the obvious solutions.

Upvotes: -1

torek
torek

Reputation: 487755

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. :-)

Upvotes: 48

Theodoros Chatzigiannakis
Theodoros Chatzigiannakis

Reputation: 29213

This is because, in the first example, the struct is previously undefined and so the compiler tries to treat this first reference to that struct as a definition.

In general, C is a language where the order of your declarations matters. Everything you use should be properly declared in advance in some capacity, so that the compiler can reason about it when it's referenced in other context.

This is not a bug or a mistake in the design of the language. Rather, it's a choice that I believe was made to simplify the implementations of the first C compilers. Forward declarations allow a compiler to translate the source code serially in one pass (as long as some information such as sizes and offsets is known). If this weren't the case, the compiler would have be able to go back and forth in the program whenever it meets an unrecognized identifier, requiring its code emission loop to be much more complex.

Upvotes: 5

user4815162342
user4815162342

Reputation: 154846

The compiler is warning you about a forward declaration of struct lol. C allows you to do this:

struct lol;     /* forward declaration, the size and members of
                   struct lol are unknown */

This is most used when defining self-referencing structs, but it is also useful when defining private structs that are never defined in the header. Because of this latter use case, it is allowed to declare functions that receive or return pointers to incomplete structs:

void foo(struct lol *x);

However, just using an undeclared struct in a function declaration, as you did, will be interpreted as a local incomplete declaration of struct lol whose scope is constrainted to the function. This interpretation is mandated by the C standard, but it is not useful (there is no way to construct the struct lol to pass to the function) and is almost certainly not what the programmer intended, so the compiler warns.

Upvotes: 7

Related Questions