-->

Const static variable defined in header file has s

2020-07-27 04:48发布

问题:

I have checked out the definition of std::ios::app in /usr/include/c++/4.6/bits/ios_base.h and found that std::ios::app is defined as a const static variable:

typedef _Ios_Openmode openmode;

/// Seek to end before each write.
static const openmode app =     _S_app;

in which _Ios_Openmode is defined in the same header file as

enum _Ios_Openmode 
{ 
  _S_app        = 1L << 0,
  _S_ate        = 1L << 1,
  _S_bin        = 1L << 2,
  _S_in         = 1L << 3,
  _S_out        = 1L << 4,
  _S_trunc      = 1L << 5,
  _S_ios_openmode_end = 1L << 16 
};

It's well known that static variable has internal linkage and every translation unit has its own copy of this static variable, which means static variables in different translation unit should have different addresses. However, I have used two separate programs to print address of std::ios::app and found that the printed address are the same:

source file test1.cpp

#include <iostream>

int main() {
    std::cout << "address: " << &std::ios::app << std::endl;
    return 0;
}

result

address: 0x804a0cc

source file test2.cpp is the same with test1.cpp and the result is the same:

address: 0x804a0cc

This really confused me, shouldn't static variables in different translation units have different addresses?


Update: as is pointed out in the comments, std::ios::app is a static data member rather than a static const variable; static data member has external linkage and addresses of static data member in different translation unit should be the same. The second point is that my method for validating this fact is wrong: different program does not mean different translation unit.

回答1:

Your test1.cpp and test2.cpp are not only two separate translation units, but you compile them into two entirely different programs and run them as separate processes. The memory space for each process is defined by the operating system anew each time you run it, and the absolute values of the addresses in each process cannot be compared. They may be identical even when you run the processes in parallel, because these addresses are interpreted relative to the virtual address space of each process. (*)

If you want to see the effect of internal linkage, you need to link the two translation units together after compiling them.

You can do this in the following way:

Define a header test.h:

const static int i = 0;

Define the implementation of a function print_i_1 in test1.cpp:

#include <iostream>
#include "test.h"

void print_i_1()
{ std::cout << &i << std::endl; }

Define the implementation of a function print_i_2 in test2.cpp:

#include <iostream>
#include "test.h"

void print_i_2()
{ std::cout << &i << std::endl; }

Note that both functions perform the same operation, but as long as they are compiled separately, they will each refer to a different instance of i.

Note also that none of these programs includes the definition of main(). We provide this in a third file, test.cpp:

extern void print_i_1();
extern void print_i_2();

int main()
{
  print_i_1();
  print_i_2();

  return 0;
}

And now you compile each .cpp file (so we have three translation units). I am using GCC, but a similar thing is possible with other compilers, too:

g++ -W -Wall -g -o test1.o -c ./test1.cpp
g++ -W -Wall -g -o test2.o -c ./test2.cpp
g++ -W -Wall -g -o test.o -c ./test.cpp

And then link them together:

g++ -W -Wall -g -o test ./test.o ./test1.o ./test2.o

The output I get when running the resulting executable, test, is:

0x4009c8
0x4009d0

Two different addresses.

Note that the keyword static is not actually required in C++ to accomplish this for const variables in namespace scope (including global namespace scope). They have internal linkage automatically, unless explicitly declared extern.


(*) As it turns out, you seem to be using the address of a static member of a class defined in the standard library. In that case, there are two remarks to be made:

  • If you link to the standard library dynamically, objects can actually be shared even between two separate processes (which, however, still does not necessarily mean that the addresses displayed will be the same, due to each process still having its own address space);
  • However, static class members have external linkage, so your assumptions would have been wrong right from the start in this case.


回答2:

9.4.2 defines the rules for static data members:

9.4.2/3 defines that a static const literal `can specify a brace-or-equal-initializer.' Meaning, that you can define it similar to X::x below.

9.4.2/4 defines that only one definition can exist (`one definition rule' (odr), see 3.2).

And finally, 9.4.2/5 further defines that all static data members of a class in namespace scope will have external linkage.

Example:

// test.h
struct X {
    static const int x = 10;
};

// main.cpp

#include <iostream>
#include "test.h"
void f();
int main(int argc, const char* argv[]) {
    std::cout << &X::x << std::endl;
    f();
    return 0;
}

// test.cpp
#include <iostream>
#include "test.h"

void f() {
    std::cout << &X::x << std::endl;
}

Output:

001A31C4
001A31C4


标签: c++ static