2

我检查了 /usr/include/c++/4.6/bits/ios_base.h 中 std::ios::app 的定义,发现 std::ios::app 被定义为 const 静态变量:

typedef _Ios_Openmode openmode;

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

其中 _Ios_Openmode 在同一个头文件中定义为

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 
};

众所周知,静态变量具有内部链接,每个翻译单元都有自己的静态变量副本,这意味着不同翻译单元中的静态变量应该有不同的地址。但是,我使用了两个单独的程序来打印 std::ios::app 的地址,发现打印的地址是一样的:

源文件 test1.cpp

#include <iostream>

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

结果

address: 0x804a0cc

源文件 test2.cpp 与 test1.cpp 相同,结果相同:

address: 0x804a0cc

这真的让我很困惑,不同翻译单元中的静态变量不应该有不同的地址吗?


更新:正如评论中指出的那样, std::ios::app 是一个静态数据成员,而不是一个静态 const 变量;静态数据成员有外部链接,不同翻译单元的静态数据成员地址应该相同。第二点是我验证这个事实的方法是错误的:不同的程序并不意味着不同的翻译单元。

4

2 回答 2

4

您的test1.cpptest2.cpp不仅是两个独立的翻译单元,而且您将它们编译成两个完全不同的程序并将它们作为单独的进程运行。每个进程的内存空间是由操作系统在每次运行时重新定义的,每个进程中地址的绝对值是无法比较的。即使您并行运行进程,它们也可能相同,因为这些地址是相对于每个进程的虚拟地址空间进行解释的。(*)

如果想看内部联动的效果,需要在编译后将两个翻译单元联动起来。

您可以通过以下方式执行此操作:

定义一个标题test.h

const static int i = 0;

在 中定义函数的print_i_1实现test1.cpp

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

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

在 中定义函数的print_i_2实现test2.cpp

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

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

请注意,这两个函数执行相同的操作,但只要分别编译它们,它们将各自引用不同的i.

另请注意,这些程序都不包含main(). 我们在第三个文件中提供了这个test.cpp

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

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

  return 0;
}

现在您编译每个 .cpp 文件(所以我们有三个翻译单元)。我正在使用 GCC,但其他编译器也可以进行类似的操作:

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

然后将它们链接在一起:

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

我在运行生成的可执行文件时得到的输出test是:

0x4009c8
0x4009d0

两个不同的地址。

请注意,staticC++ 中实际上并不需要关键字来为const命名空间范围(包括全局命名空间范围)中的变量完成此操作。它们自动具有内部链接,除非明确声明extern


(*)事实证明,您似乎正在使用标准库中定义的类的静态成员的地址。在这种情况下,有两点需要注意:

  • 如果您动态链接到标准库,对象实际上甚至可以在两个单独的进程之间共享(然而,这并不一定意味着显示的地址将是相同的,因为每个进程仍然有自己的地址空间);
  • 但是,静态类成员具有外部链接,因此在这种情况下,您的假设从一开始就是错误的。
于 2013-07-25T03:19:01.600 回答
1

9.4.2 定义静态数据成员的规则:

9.4.2/3 定义了一个静态常量字面量“可以指定一个大括号或相等初始化器”。这意味着,您可以将其定义为类似于下面的 X::x。

9.4.2/4 规定只能存在一个定义(“一个定义规则”(odr),见 3.2)。

最后,9.4.2/5 进一步定义了命名空间范围内类的所有静态数据成员都将具有外部链接。

例子:

// 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;
}

输出:

001A31C4
001A31C4
于 2013-07-25T02:52:03.190 回答