首页 > 解决方案 > 为什么链接器在链接共享和静态时不会抱怨多个定义

问题描述

我最近在我正在开发的代码中遇到了静态初始化失败的问题。这让我意识到我缺乏关于链接过程和全局变量初始化的知识,所以我在 cppcon上发现了这个非常有趣的关于全局变量、链接以及将静态库嵌入共享和同时链接两者的可能问题(使用全局变量定义)。基本上说的是,当具有以下结构时:

//static.hpp
#pragma once

#include <string>

extern std::string globalString;

//static.cpp
#include "static.hpp"

std::string globalString;

//shared.hpp
#pragma once

#include "static.hpp"
#include <string>

std::string& getGlobalString();

//shared.cpp
#include "shared.hpp"
#include "static.hpp"

std::string& getGlobalString(){
  return globalString;
}

//main.cpp
#include "static.hpp"
#include "shared.hpp"
#include <iostream>

int main(){
    std::cout << "Global variable: " << globalString <<  std::endl;
    std::cout << "Global var acccesed through shared lib: " << getGlobalString() << std::endl;
    return 0;
}

所有编译:

clang++ -c -std=c++14 -fpic static.cpp
clang++ -c -std=c++14 -fpic shared.cpp
clang++ -c -std=c++14 main.cpp
ar rsc libstatic.a static.o
clang++ -shared -o libshared.so shared.o -L./ -lstatic
clang++ -L./ -Wl,-rpath=./ main.o -lstatic -lshared

由于多次调用同一对象的构造函数和析构函数,我导致分段错误。这是令人惊讶的,因为我认为每个对象构造函数都将被调用一次是不变的!

标签: c++compilationlinker

解决方案


这是令人惊讶的,因为我认为每个对象构造函数都将被调用一次是不变的!

在正确构造的二进制文件中,这是正确的。在具有 ODR 违规的二进制文件中不一定是正确的。

关于多次调用构造函数/析构函数的标准是什么?

该标准规定,在正确构造的程序中,构造函数/析构函数只被调用一次。在违反 ODR 的程序中,任何事情都可能发生(未定义的行为)。

如果在这种情况下,全局静态 i 嵌入在 main.o 和 libshared.so 中,这是否意味着有一个定义的规则被打破了?

是的。

为什么链接器在链接 main 时不抱怨第二个定义可用?

就链接器而言,您的程序没有任何问题。从链接器的角度来看,在静态库和共享库中定义一个全局是完全合乎情理的。另请参阅此答案

当在单独的翻译单元中至少有两个全局时,建立构造和销毁顺序的建议方法之一是在第一次使用时进行初始化。然而,据我所知,这只能确保施工顺序。

正确的。这是针对不同问题的解决方案:两个全局变量 A 和 B 在单独的翻译单元中定义。

你没有那个问题,你有一个完全不同的问题(违反 ODR)。

毁灭令呢?如何控制?

在正确构造的程序中,破坏顺序保证与构造顺序相反。


推荐阅读