Search code examples
c++ctypeslanguage-lawyerlinkage

Clarification on difference in ODR rules for structs in C and C++


I am aware of how ODR, linkage, static, and extern "C" work with functions. But I am not sure about visibility of types since they cannot be declared static and there are no anonymous namespaces in C.

In particular, I would like to know the validity of the following code if compiled as C and C++

// A.{c,cpp}
typedef struct foo_t{
    int x;
    int y;
} Foo;

static int use_foo() 
{ 
    Foo f;
    f.x=5;
    return f.x;
}
// B.{c,cpp}
typedef struct foo_t{
    double x;
} Foo;

static int use_foo() 
{ 
    Foo f;
    f.x=5.0;
    return f.x;// Cast on purpose
}

using the following two commands (I know both compilers autodetect the language based on extensions, hence the different names).

  • g++ -std=c++17 -pedantic -Wall -Wextra a.cpp b.cpp
  • gcc -std=c11 -pedantic -Wall -Wextra a.c b.c

Versions 8.3 happily compile both without any errors. Clearly, if both struct symbols have external linkage, there is ODR violation because the definitions are not identical. Yes, compiler is not required to report it, hence my question because neither did.

Is it valid C++ program?

I do not think so, that is what anonymous namespaces are for.

Is it valid C program?

I am not sure here, I have read that types are considered static which would make the program valid. Can someone please confirm?

C,C++ Compatibility

If these definitions were in public header files, perhaps in different C libraries, and a C++ program includes both, each also in a different TU, would that be ODR? How can one prevent this? Does extern "C" play any role?


Solution

  • I will use for references the n1570 draft for C11 for the C language and the draft n4860 for C++20 for the C++ language.

    1. C language

      Types have no linkage in C: 6.2.2 Linkages of identifiers §6:

      The following identifiers have no linkage: an identifier declared to be anything other than an object or a function...

      That means that the types used in a.c and b.c are unrelated: you correctly declare different objects in both compilation units.

    2. C++ language

      Types do have linkage in C++. 6.6 Program and linkage [basic.link] says (emphasize mine):

      • §2:

      A name is said to have linkage when it might denote the same object, reference, function, type, template, namespace or value as a name introduced by a declaration in another scope

      • §4

      An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above and that is the name of
      ...
      a named class...
      ...
      has its linkage determined as follows:
      — if the enclosing namespace has internal linkage, the name has internal linkage;
      — otherwise, if the declaration of the name is attached to a named module (10.1) and is not exported (10.2), the name has module linkage;
      — otherwise, the name has external linkage

      The types declared in a.cpp and b.cpp share the same identifier with external linkage and are not compatible: the program is ill-formed.


    That being said, most common compiler are able to compile either C or C++ sources, and I would bet a coin that they try hard to share most of the implementation of both languages. For that reason, I would trust real world implementation to produce the expected resuls even for C++ language. But Undefined Behaviour does not forbid expected results...