Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Am I guaranteed to not be bitten by this ODR violation?

I have a header file that declares a template with a static variable and also defines it:

/* my_header.hpp */
#ifndef MY_HEADER_HPP_
#define MY_HEADER_HPP_

#include <cstdio>

template<int n>
struct foo {
    static int bar;

    static void dump() { printf("%d\n", bar); }
};

template<int n>
int foo<n>::bar;

#endif // MY_HEADER_HPP_

This header is included both by main.cpp and a shared library mylib. In particular, mylib_baz.hpp just includes this template and declares a function that modifies a specialization of the template.

/* mylib_baz.hpp */
#ifndef MYLIB_BAZ_HPP_
#define MYLIB_BAZ_HPP_

#include "my_header.hpp"

typedef foo<123> mylib_foo;
void init_mylib_foo();

#endif // MYLIB_BAZ_HPP_

and

/* mylib_baz.cpp */
#include "mylib_baz.hpp"
void init_mylib_foo() {
    mylib_foo::bar = 123;
    mylib_foo::dump();
};

When I make mylib.so (containing mylib_baz.o), the symbol for foo<123>::bar is present and declared weak. However, the symbol for foo<123>::bar is declared weak also in my main.o:

/* main.cpp */
#include "my_header.hpp"
#include "mylib_baz.hpp"

int main() {
    foo<123>::bar = 456;
    foo<123>::dump(); /* outputs 456 */
    init_mylib_foo(); /* outputs 123 */
    foo<123>::dump(); /* outputs 123 -- is this guaranteed? */
}

It appears that I am violating one definition rule (foo<123>::bar defined both in my_header.cpp and main.cpp). However, both with g++ and clang the symbols are declared weak (or unique), so I am not getting bitten by this -- all accesses to foo<123>::bar modify the same object.

Question 1: Is this a coincidence (maybe ODR works differently for static members of templates?) or am I in fact guaranteed this behavior by the standard?

Question 2: How could I have predicted the behavior I'm observing? That is, what exactly makes the compiler declare symbol weak?

like image 405
FreenodeForsakeMe Avatar asked Nov 10 '22 04:11

FreenodeForsakeMe


1 Answers

There is no ODR violation. You have one definition of bar. It is here:

template<int n>
int foo<n>::bar; // <==

As bar is static, that indicates that there is one definition across all translation units. Even though bar will show up once in all of your object files (they need a symbol for it, after all), the linker will merge them together to be the one true definition of bar. You can see that:

$ g++ -std=c++11 -c mylib_baz.cpp -o mylib_baz.o
$ g++ -std=c++11 -c main.cpp -o main.o
$ g++ main.o mylib_baz.o -o a.out

Produces:

$ nm mylib_baz.o | c++filt | grep bar
0000000000000000 u foo<123>::bar
$ nm main.o | c++filt | grep bar
0000000000000000 u foo<123>::bar
$ nm a.out | c++filt | grep bar
0000000000601038 u foo<123>::bar

Where u indicates:

"u"
The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.

like image 185
Barry Avatar answered Nov 14 '22 22:11

Barry