15

I was bug-fixing some code and the compiler warned (legitimately) that the function dynscat() was not declared — someone else's idea of an acceptable coding standard — so I tracked down where the function is defined (easy enough) and which header declared it (none; Grrr!). But I was expecting to find the details of the structure definition were necessary for the extern declaration of qqparse_val:

extern struct t_dynstr qqparse_val;

extern void dynscat(struct t_dynstr *s, char *p);
extern void qqcat(char *s);

void qqcat(char *s)
{
    dynscat(&qqparse_val, s);
    if (*s == ',')
        dynscat(&qqparse_val, "$");
}

The qqcat() function in the original code was static; the extern declaration quells the compiler warning for this snippet of the code. The dynscat() function declaration was missing altogether; again, adding it quells a warning.

With the code fragment shown, it's clear that only the address of the variable is used, so it makes sense at one level that it does not matter that the details of the structure are not known. Were the variable extern struct t_dynstr *p_parseval;, you'd not be seeing this question; that would be 100% expected. If the code needed to access the internals of the structure, then the structure definition would be needed. But I'd always expected that if you declared that the variable was a structure (rather than a pointer to the structure), the compiler would want to know the size of the structure — but apparently not.

I've tried provoking GCC into complaining, but it doesn't, even GCC 4.7.1:

gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c

The code has been compiling on AIX, HP-UX, Solaris, Linux for a decade, so it isn't GCC-specific that it is accepted.

Question

Is this allowed by the C standard (primarily C99 or C11, but C89 will do too)? Which section? Or have I just hit on an odd-ball case that works on all the machines it's ported to but isn't formally sanctioned by the standard?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • if dynstr.h is defined somewhere it should be ok, no? – Shark Aug 30 '12 at 15:18
  • 2
    @Shark: The code is exactly what's shown; no headers needed. – Jonathan Leffler Aug 30 '12 at 15:19
  • 2
    What exactly is surprising? That you can take the address of an object with incomplete type? – Jens Aug 30 '12 at 15:48
  • 3
    This question contains a great deal of unnecessary information. I suggest pruning it to the essentials, such as: “Why is `extern struct foo;… /* Now inside a function. */ &foo` allowed by the C standard, and which sections are informative about this?” – Eric Postpischil Aug 30 '12 at 15:58

6 Answers6

15

What you have is an incomplete type (ISO/IEC 9899:1999 and 2011 — all these references are the same in both — §6.2.5 ¶22):

A structure or union type of unknown content (as described in §6.7.2.3) is an incomplete type.

An incomplete type can still be an lvalue:

§6.3.2.1 ¶1 (Lvalues, arrays, and function designators)

An lvalue is an expression with an object type or an incomplete type other than void; ...

So as a result it's just like any other unary & with an lvalue.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • 6
    I'm surprised this is not the accepted answer, since the question included, "Which part of the C standard ..." and "Which section?". The accepted answer doesn't mention which part or which section. – Dan Moulding Apr 11 '13 at 15:35
8

Looks like a case of taking the address of an object with incomplete type.

Using pointers to incomplete types is totally sane and you do it each time you use a pointer-to-void (but nobody ever told you :-)

Another case is if you declare something like

extern char a[];

It is not surprising that you can assign to elements of a, right? Still, it is an incomplete type and compilers will tell you as soon as you make such an identifier the operand of a sizeof.

Flexo
  • 87,323
  • 22
  • 191
  • 272
Jens
  • 69,818
  • 15
  • 125
  • 179
  • 2
    Why horror? You can use incomplete typed structs to your advantage, e.g. to create 'opaque types' where you hide the innards of, e.g. what's in a `struct FILE`. All access happens via functions taking a pointer to the incomplete type, which is what the Standard IO library does. – Jens Aug 31 '12 at 07:18
  • So in essence, the fact that the compiler did not complain about Jonathan's first line of code implies another possibility for an error occurring when executed. – Joe R. Oct 20 '12 at 00:56
  • @Frank: Actually, it reduces the possibilities for error, since the compiler will not allow code to perform any action on a `struct t_dynstr` whose behavior would be in any way affected by the definition of that structure. If the OP's code used the type in a way which depended upon the struct contents, and the struct were later modified without recompiling OP's code, that could cause Undefined Behavior. By contrast, leaving `struct t_dynstr` as an opaque type lets the compiler ensure that no such usages exist. – supercat Dec 12 '13 at 20:46
5

your line

extern struct t_dynstr qqparse_val;

Is an external declaration of an object, and not a definition. As an external object it "has linkage" namely external linkage.

The standard says:

If an identifier for an object is declared with no linkage, the type for the object shall be complete by the end of its declarator, ...

this implies that if it has linkage the type may be incomplete. So there is no problem in doing &qqparse_val afterwards. What you wouldn't be able to do would be sizeof(qqparse_val) since the object type is incomplete.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
4

A declaration is necessary to "refer" to something. A definition is necessary to "use" something. A declaration may provide some limited definition as in "int a[];" What stumps me is:

int f(struct _s {int a; int b;} *sp)
{
    sp->a = 1;
}

gcc warn the 'struct _s' is declared inside parameter list. And states "its scope is ONLY this definition or declaration,...". However it does not give an error on "sp->a" which isn't in the parameter list. When writing a 'C' parser I had to decide where the definition scope ended.

Dan Wood
  • 41
  • 1
  • Hi Dan, Welcome to Stack Overflow. There are reasons why it a pure function declaration the scope of the structure is the prototype argument list. If you define a function that way, then all the variables (and by extension types) defined in the prototype heading of the function are in scope for the body of the function. The reason for the warning is related to whether the type can be used by any other function to call this one. That gets into some tricky reading of the standard (_§6.2.7 Compatible type and composite type_ in C 2011, with x-refs to §6.7.2, §6.7.3 and §6.7.6 too). – Jonathan Leffler Oct 24 '12 at 23:19
3

Focusing on the first line:

extern struct t_dynstr qqparse_val;

It can be divided into the separate steps of creating the type and the variable, resulting in this equivalent pair of lines:

struct t_dynstr; /* declaration of an incomplete (opaque) struct type */
extern struct t_dynstr qqparse_val; /* declaration of an object of that type */

The second line looks just like the original, but now it's referring to the type that already exists because of the first line.

The first line works because that's just how opaque structs are done.

The second line works because you don't need a complete type to do an extern declaration.

The combination (second line works without the first line) works because combining a type declaration with a variable declaration works in general. All of these are using the same principle:

struct { int x,y; } loc; /* define a nameless type and a variable of that type */
struct point { int x,y; } location; /* same but the type has a name */
union u { int i; float f; } u1, u2; /* one type named "union u", two variables */

It looks a little funny with the extern being followed immediately by a type declaration, like maybe you're trying to make the type itself "extern" which is nonsense. But that's not what it means. The extern applies to the qqparse_val in spite of their geographical separation.

Alan Curry
  • 14,255
  • 3
  • 32
  • 33
2

Here's my thoughts relative to the standard (C11).

Section 6.5.3.2: Address and indirection operators

Constraints

Paragraph 1: The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.

Paragraph 2: The operand of the unary * operator shall have pointer type.

Here, we don't specify any requirement on the object, other than that it is an object (and not a bitfield or register).

On the other hand, let's look at sizeof.

6.5.3.4 The sizeof and _Alignof operators

Constraints

Paragraph 1: 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. The _Alignof operator shall not be applied to a function type or an incomplete type.

Here, the standard explicitly requires the object to not be an incomplete type.

Therefore, I think this is a case of what is not explicitly denied is allowed.

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • My surprise is that the first line of code is acceptable; given that it is acceptable, the subsequent code is clearly legitimate. So your observations are valid (thank you) for the 'rest of the code', but the puzzle is the first line (and I may just be being obtuse; it wouldn't be the first time), but your quotes from the standard don't really address the first line. – Jonathan Leffler Aug 30 '12 at 17:03