首页 文章

头文件中定义的Const静态变量在不同的转换单元中具有相同的地址

提问于
浏览
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变量;静态数据成员具有外部链接,不同翻译单元中静态数据成员的地址应相同 . 第二点是我验证这个事实的方法是错误的:不同的程序并不意味着不同的翻译单位 .

2 回答

  • 1

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

    如果要查看内部链接的效果,则需要在编译后将两个翻译单元链接在一起 .

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

    定义 Headers test.h

    const static int i = 0;
    

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

    #include <iostream>
    #include "test.h"
    
    void print_i_1()
    { std::cout << &i << std::endl; }
    

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

    #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
    

    两个不同的地址 .

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


    (*)事实证明,您似乎正在使用标准库中定义的类的静态成员的地址 . 在这种情况下,有两个评论要做:

    • 如果动态链接到标准库,实际上甚至可以在两个单独的进程之间共享对象(但是,由于每个进程仍然有自己的地址空间,但这仍然不一定意味着显示的地址是相同的) ;

    • 但是,静态类成员具有外部链接,因此在这种情况下,您的假设从一开始就是错误的 .

  • 3

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

    9.4.2 / 3定义了一个静态const文字`可以指定一个括号或等于初始化器' . 意思是,您可以将其定义为类似于下面的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
    

相关问题