5

Consider this code snippet:

#include <threads.h>

int thread_start(void *ptr)
{
    struct {
        int a;
        int b;
    } *data = ptr;

    return 0;
}

int main(int argc, char *argv[])
{
    struct {
        int a;
        int b;
    } data;

    thrd_t thread;
    thrd_create(&thread, thread_start, &data);
    thrd_join(thread, NULL);
    return 0;
}

Regarding the casting of a void * to struct { int; int } *, assuming that the fields of the anonymous structure are identical to the fields of the structure that was initially allocated, is this well defined behavior according to the C standard?

dbush
  • 205,898
  • 23
  • 218
  • 273
Jim Morrison
  • 401
  • 4
  • 9
  • I would imagine so but can't find an explicit reference in the Standard. Others may well do better than I have done, though. – Adrian Mole Jun 23 '21 at 16:33
  • Does this answer your question? [Compatible types and structures in C](https://stackoverflow.com/questions/2904668/compatible-types-and-structures-in-c) It appears that they aren't. – Eugene Sh. Jun 23 '21 at 16:34
  • @EugeneSh. I think that suggests that they aren't compatible terms of direct assignment. However, casting and re-casting *via* a `void*` is a bit of a different case. After all, they both *must* have the same aliasing/alignment requirements, etc. – Adrian Mole Jun 23 '21 at 16:36
  • I remember reading somewhere that two structures are equivalent if and only if they are exactly the same character by character. Don't remember if it was only for tagged or for all (including anonymous) structures. – Some programmer dude Jun 23 '21 at 16:38

1 Answers1

6

These structs would be compatible if they were not in the same file.

Had thread_start been defined in a separately compiled .c file (i.e. a separate translation unit) then they would be compatible. Specifically, the names, types, and order of the members are the same, and they both have the same tag which in this case is no tag.

Section §6.2.7 ¶1 of the C standard lays out these requirements:

Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators. Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types; if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier; and if one member of the pair is declared with a name, the other is declared with the same name. For two structures, corresponding members shall be declared in the same order. For two structures or unions, corresponding bit-fields shall have the same widths. For two enumerations, corresponding members shall have the same values.

So these structs would be compatible if they were not in the same file. But because they are, the structs are not compatible.

§6.7.2.1 ¶8 says:

The presence of a struct-declaration-list in a struct-or-union-specifier declares a new type, within a translation unit.

So, §6.7.2.1 specifies what happens in a single translation unit. The specification in §6.2.7 overrides the specification in §6.7.2.1 when the declarations are in separate translation units.

NB: It's easy to confuse 6.2.7 and 6.7.2.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Where, in what you have quoted, does it *explicitly* preclude structures defined in the same translation unit? One could argue that the mention of "separate" units is just for clarity over those in the same. – Adrian Mole Jun 23 '21 at 16:41
  • Maybe this (and the suggested dupe) need the Language Lawyer tag: *absence of evidence is not evidence of absence?* – Adrian Mole Jun 23 '21 at 16:43
  • 1
    @AdrianMole Wouldn't the part "*declared in separate translation units*" be completely redundant if it wasn't to differentiate between two cases? – Eugene Sh. Jun 23 '21 at 16:50
  • @EugeneSh. Maybe. I'm not a C Language-Lawyer. But, one *could* read it as: "**even if** declared in separate translation units." Just sayin'. – Adrian Mole Jun 23 '21 at 16:52
  • @AdrianMole — No; the words 'even if' cannot be inferred from the definitional language "two structure, union or enumerated types declared in separate translation units". – Jonathan Leffler Jun 23 '21 at 16:53
  • @JonathanLeffler Yeah - I can actually see that. However, why would the rule be more restrictive for stuff in the same unit, over stuffs separated? – Adrian Mole Jun 23 '21 at 16:55
  • 2
    It kind of begs the question that if the two identical anonymous struct types are defined in translation unit "a", and another identical anonymous struct type is defined in translation unit "b", which one in "a" is compatible with the one in "b"? – Ian Abbott Jun 23 '21 at 16:57
  • @IanAbbott Wow. Looks like a can of worms to me. Unless these are not compatible even if declared in different TUs. – Eugene Sh. Jun 23 '21 at 16:58
  • @AdrianMole: this is overriding (relaxing) a restriction elsewhere. C11 [§6.7.2.1 Structure and union specifiers ¶8](http://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p8) says: _The presence of a struct-declaration-list in a struct-or-union-specifier declares a new type, within a translation unit._ It means that you can't define a structure type inside one function and use it in any other function in the same TU, even if spelled identically. You must define the structure type at file scope if the type is used in more than one function. – Jonathan Leffler Jun 23 '21 at 16:59
  • @IanAbbott: The types with local (function) scope in each TU have local scope and cannot be accessed outside that scope. The cross-TU stuff only works when the types are defined at file scope. – Jonathan Leffler Jun 23 '21 at 17:00
  • We *definitely* need `language-lawyer` here, then. – Adrian Mole Jun 23 '21 at 17:01
  • @JonathanLeffler But two identical anonymous structs can be defined in the file scope in the same TU – Eugene Sh. Jun 23 '21 at 17:01
  • 2
    @EugeneSh. I guess the way out of that can of worms is that ultimately it is objects that are being compared for type compatibility. So `struct { int a; int b; } foo;` in translation unit "a" would be compatible with `extern struct { int a; int b; } foo;` in translation unit "b", but neither is compatible with `struct {int a; int b; } bar;`. And also, `typedef struct { int a; int b } tfoo;` in translation unit "a" would be compatible with the identical type definition in translation unit "b". – Ian Abbott Jun 23 '21 at 17:03
  • @EugeneSh. — hmmm, yes. Ugh! Fortunately, only those with too much time on their hands would spend much time worrying about such a problem. Can you devise an example where it would matter? At file scope in TU-1: `struct { … } a; struct { … } b;` — in TU-2: `extern struct { … } a; extern struct { … } b;` — that doesn't do it, but I suspect there's something along those lines. – Jonathan Leffler Jun 23 '21 at 17:05
  • @JonathanLeffler, IanAbbott Good point. I can't come up with even a nonsensical use-case. Maybe someone else can.. – Eugene Sh. Jun 23 '21 at 17:10
  • @IanAbbott: Re “which one in "a" is compatible with the one in "b"?”: Each structure type in “a” is compatible with the structure type in “b”. At the same time, neither structure type in “a” is compatible with the other; “compatible” is not required to be a transitive relationship. – Eric Postpischil Jun 23 '21 at 22:38
  • 1
    @JonathanLeffler: What sort of use case are you looking for? I expect the rules here arise out of practical realities. Given a header containing `typedef struct { float x, y; } Point;` included in multiple translation units, we want `Point` to be the “same” type, hence a compatible type, in those translation units. Thus justifies the cross-unit rule. At the same time, if `typedef struct { float re, im; } Complex;` is declared in one unit, there is a benefit to treating it as a different and incompatible type from `Point` and reporting errors if code attempts an assignment of one to the other… – Eric Postpischil Jun 23 '21 at 22:44
  • … Of course it would be a further benefit if `Point` and `Complex` could be distinguished across units, but the limitations of existing object module formats precluded that when the rules about this in the C standard were being written. – Eric Postpischil Jun 23 '21 at 22:46
  • @EricPostpischil: Adding `typedef` completely changes the problem(s). It is effectively irrelevant to the question. Or, put another way, any use case of interest would not include any use of `typedef`. – Jonathan Leffler Jun 23 '21 at 22:47
  • @JonathanLeffler: Again: What sort of a use case are you looking for? The `typedef` use case explains why the rules are the way they are. I did not say it is the problem in the question; I gave it as a use case that explains the rules. – Eric Postpischil Jun 23 '21 at 22:56
  • @EricPostpischil Agreed. We still have an anonymous struct, it just also happens to have an alias. – dbush Jun 23 '21 at 22:59
  • @EricPostpischil: I know `typedef` completely changes things; I said so. The question I raised is whether someone can give an example which uses two or more `struct /*no tag*/ { /* details */ }` in two separate translation units (TU-1 and TU-2) and they can cause confusion as to which type in TU-1 is referenced in TU-2, as asked by Ian Abbott. To be clear: there is no `typedef`; to be significant (usable), there must be either a structure tag, which I've said is not present, or a list of variables (otherwise the declaration is pointless); the details must be the same too. – Jonathan Leffler Jun 23 '21 at 23:05
  • @JonathanLeffler `typedef` is not completely different if you think of any invocation of a storage class specifier or a default storage class on an anonymous struct type as pinning down that type. (`typedef` is treated as a storage class specifier after all, syntactically.) – Ian Abbott Jun 24 '21 at 09:50