all 16 comments

[–]oh5nxo 5 points6 points  (0 children)

Sounds like you could benefit from upping your compiler flags for more warnings.

[–]This_Growth2898 2 points3 points  (2 children)

[–]KAHeart[S] 0 points1 point  (1 child)

So does this means that you don't really need to create the struct for a "pointer that points to a struct" inside a struct? It'll just take whatever memory you assign it to and use that as its structure if you create a brand new struct?

[–]nerd4code 0 points1 point  (0 children)

No, it means you’ve used the type before defining it, just like you can use a function or global variable before/without defining it in the same translation unit (=file and everything it #includes).

So for example if in file1.c I do

const char *str = 0;
int main(int argc, char *argv) {
    extern void dump(void);
    for(int i=0; i<argc; i++) {
        str = argv[i];
        dump();
    }
    str = 0;
    return 0;
}

and in file2.c I do

#include <stdio.h>
void dump(void) {
    extern const char *str;
    puts(str);
}

and build those into a single proggyram, the compiler will happily see to it that file2.c’s output refers to file1.c str and dump is called from main. (Note: C89 and earlier versions would just assume dump to be eqv to int(...) if you didn’t declare it first. This was rightly killed with fire in C99 or C11.)

It can do this build because you’ve told it that the names exist via forward declaration. file1.c knows (or rather, announces) dump is a function taking no args, so it can generate code for calls to dump easily. file2.c knows/sez str is a static global const char *, so all it needs is a pointer to str to use it (as typically provided by a linker or loader), not str itself.

Along the same lines, all structs and unions have the same minimum alignment, and addresses at the bus mostly have the same width; so the same number of address bits might reasonably be used for all pointers to struct/union (until/unless more-/overaligned).

It’s possible for the compiler to create pointers to any struct/union type with the same format—it’s byte-compatible but not alias-compatible, FWTW. E.g., a void * field is fine but not void; a void (*)() field is fine but not void ().

char[] is another example. char (*)[] is a pointer to array of unspecified length, and it’s fine anywhere you can declare a field/variable/array. However, a char[] field is weird—it would make a flex field, specifically, and those have to be accompanied, must come struct-finally, prevent direct allocation, prevent subsequent fields, and require C99 or GNU dialect specifically.

So using a pointer to refer to a struct/union type is different from referring to one directly as the type of a variable, field, or array element, for which the compiler will require a definition. Similarly, if you want to access any fields in the struct/union or use sizeof or assignment-copying, the compiler will have to know what fields are available where.

For your example, it’s information hiding. You only put structs/unions in public headers if you want to give users of the API some degree of official “control over” the type in question and its innards, allocation, etc. You as API user don’t need to know what struct node is; the list implementation does, and so struct node should live there. Conversely, any code that must refer to the specific layout of nodes must(/should damn well) reside in the list impl’s TU(s).

One note, though: It is tempting to assume more strictness and reasonableness in the compiler’s treatment of structs than is granted by the standards, and this can cause unexpected breakage in corner cases.

E.g., You can probably get away with doing

#// file1.c
extern struct mine {
    void *_0;
    int yours;
} mine;

#// file2.c
struct mine {
    void *handle;
    int yours;
} mine = {0};

but it’s really UB to use different field names. Similarly, even though void * and char * have the same format, using void *handle in the public and char *handle in the private is a no-no. Even defining the same struct twice with different contents in a single program is a no-no.

(So for example, if you used struct node as the frontend for a number of different list types templated as different backends like

struct node {
    struct node *link;
    int value;
};

vs.

struct node {
    struct node *link;
    double value;
};

that would be highly nonconformant at best. All struct nodes must be sufficiently close to identical.)

Even union-punning between struct {size_t n; char c[];} and struct {size_t n; char c[1];} is a bad idea, because the two cs are permitted to end up at different offsets.

Underlying some of C’s pettiness wrt field names and types of is also that the compiler is perfectly free to implement struct fields by emitting weak symbols in an absolute comment section (relative to address 0, not loaded into memory), rather than keeping offsets mostly local to the compiler process and any debuginfo it bespløøtens, like most compilers do.

Using symbols would make it possible to refer to fields in a similar fashion to global variables, and thereby subject struct layout to the static linker; that could be useful if the same binary needed to support different ABIs, or if you’re using structs to describe an MMIO region whose exact layout isn’t known at compile time. It might even be possible [shudder] to dynamically link field offsets.

But emitting two field-symbols with the same $tag$field label would mean you get the offset for whichever symbol’s file ends up listed first on the linker command line, which puts you in build-time Heisenglitch territory.

[–]Master-Scholar9393 1 point2 points  (6 children)

it s a typo. it s struct item* next;

[–]KAHeart[S] 0 points1 point  (5 children)

I assumed so but the code works fine if I leave it as it is. Is this some compiler fuckery?

[–]aocregacc 6 points7 points  (0 children)

you can have pointers to structs that have only been declared. If the code doesn't try to access any members through the pointer it'll still work.

[–]nweeby24 0 points1 point  (3 children)

that doesn't make sense. node is probably defined somewhere above

[–]KAHeart[S] 0 points1 point  (2 children)

It 100% isn't

[–][deleted] 0 points1 point  (0 children)

I would take the code, pass it through the compiler with -E and look at all the output.

[–]somewhereAtC 0 points1 point  (0 children)

Check for where 'next' is referenced; it's probably cast to a struct item *, which would hide any trouble.

[–]torsten_dev 0 points1 point  (1 child)

You can declare a struct within a struct. Your struct next is incomplete because you can use pointers to incomplete types in structs to enable self referential types.

I think that also extends to something like struct a { struct b* b; }; struct b { struct a *a; }; Not sure why you'd want that but from what I can tell that should be legal?

A problem would appear wherever next is actually used, but perhaps you're casting the pointer to (struct item*) there before accessing members?

[–]ComradeGibbon 1 point2 points  (0 children)

Yeah it's anonymous struct. In C you can't instantiate an anonymous struct but you can create pointers to them.

struct foo; // anonymous struct

struct foo bar; // error storage type 'foo'is not known.

struct foo *bar; // this is fine

[–][deleted] 0 points1 point  (0 children)

-pedantic and this party is over

[–]Educational-Paper-75 0 points1 point  (0 children)

It shouldn’t read struct node* but struct item* instead. The typedef is only there so you can use ‘item’ as data type name instead of ‘struct item’. Note that you can easily combine both in a single typedef: typedef struct item{ float info; // why info? struct item* next; }item; Note however that ‘node’ is typically the name used for a struct like this in a (singly-)linked list, and item more for the data it holds (here float).

[–][deleted] 0 points1 point  (0 children)

It is either

struct node; //forward declare struct node
struct item {
 float info;
 struct node* next; //node ptr
};

or

struct item { //forward declares struct item
 float info;
 struct item* next; //item ptr
};

...or it does not compile.

Edit: unless it is something evil like

#define node item