0

I have some simple codes here:

// main.cpp
#include <iostream>
#include "foo.h"

int main() {
    Foo foo;
    foo.spam();
    std::cout << "In main: sizeof (Foo) = " << sizeof (Foo) << std::endl;
    return 0;
}
// foo.h
#ifndef FOO_H
#define FOO_H

class Foo {
public:
    int bar;
    void spam();
};

#endif
// foo.cpp
#include <iostream>
#include "foo.h"

void Foo::spam() {
    std::cout << "sizeof (Foo) = " << sizeof (Foo) << std::endl;
}

The program is simple and we can easily foresee the output:

sizeof (Foo) = 4
In main: sizeof (Foo) = 4

But as for the include directives, the preprocessor simply replace them with the included file content, so the declaration of Foo appears in both main.cpp and foo.cpp. What if we manually put the declaration of Foo in main.cpp and modify it on purpose like this?

// main.cpp
#include <iostream>

// #include "foo.h" replaced with declaration of foo
#ifndef FOO_H
#define FOO_H

class Foo {
public:
    int bar, baz, qux, eggs; // added some members
    void spam();
};

#endif

int main() {
    Foo foo{111, 222, 333, 444};
    foo.spam();
    std::cout << "In main: sizeof (Foo) = " << sizeof (Foo) << std::endl;
    std::cout << "foo.qux = " << foo.qux << std::endl; // try to access the newly added qux member
    return 0;
}

Now the two declarations of Foo in main.cpp and foo.cpp are different, but the program still compiles and runs! The output becomes

sizeof (Foo) = 4
In main: sizeof (Foo) = 16
foo.qux = 333

What the hell is happening here?! Why can Foo have two different declarations in one program? Why this even compiles?

271828
  • 27
  • 4
  • Why wouldn't this compile? `foo.cpp` and `main.cpp` are compiled as separate entities. They don't depend on each other. The process of linking happens after compilation. And searching for a symbol `Foo::spam` happens during linking, when no headers exist anyway. It's just how C++ works. – freakish Jun 17 '20 at 14:28
  • If you have the same preprocessor checks for `FOO_H` then only one of the definitions is seen in `main`. Otherwise you'll get `symbol multiply defined` error or something similar. – DNT Jun 17 '20 at 14:29
  • @DNTSo, that is to say, if I remove the include guard and use the header how it's expected to be used (included rather than maually pasted), the program will run normally, but there's still UB caused by multiple definition? The program runs normally simply because of luck? – 271828 Jun 17 '20 at 14:38

2 Answers2

2

Your code has undefined behavior. In C++ there is a rule called the One Definition Rule. Basically it states that there can only be one definition of a thing, and any violation of this is ill formed - no diagnostic required.

Since you have two definitions of Foo when you add one to main.cpp, you're in violation and any result you get is "correct".

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
2
class Foo;

is a declaration, but

class Foo {};

is a definition.

So you have multiple definitions of Foo in your program. This violates the one-definition-rule, which states (among other things), that every entity in your program must have exactly one definition.

Violating ODR leads to undefined behavior, so there's no point in reasoning about the output of your program.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • But what about the previous normal program? foo.h is included in both main.cpp and foo.cpp, and the preprocessor replaces it's inclusion with `class Foo { ... };`, why this is not multiple definition? – 271828 Jun 17 '20 at 14:31
  • 1
    Because each translation unit has only one definition. The linker will see multiple definitions, but it will ignore all but one of them. – cigien Jun 17 '20 at 14:33
  • 1
    @271828 One of the exceptions of the one definition rule is that duplicates (meaning the same class defined the same way) are allowed. In the second case the two definitions are not exactly the same, so you are in violation of the rule. – NathanOliver Jun 17 '20 at 14:39
  • @NathanOliver: So, that is to say duplicate definitions is allowed for some kind of entities, e.g. classed, but as the name suggests all of the definitions must be exactly the same? I said 'for some kind entities' because if I write `int a;` in a header and include it in different files, the compiler will throw an error. Did I get your points right? – 271828 Jun 17 '20 at 15:12
  • @271828 Yeah. Not everything is allowed to have multiple exact definitions. Class can, variables can't. There are more exceptions and edge cases, and there is also `inline` which allows you to have multiple definitions of variables. Have a read through [here](https://en.cppreference.com/w/cpp/language/definition) about how it works. – NathanOliver Jun 17 '20 at 15:18