惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News | PayPal Newsroom
S
Security Affairs
T
Tor Project blog
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
Help Net Security
Help Net Security
U
Unit 42
云风的 BLOG
云风的 BLOG
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
量子位
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
腾讯CDC
AI
AI
Last Week in AI
Last Week in AI
Latest news
Latest news
Google Online Security Blog
Google Online Security Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
酷 壳 – CoolShell
酷 壳 – CoolShell

I'm OWenT

国产大模型(GLM 5.1、Kimi K2.6)真实场景效果和 Coding Plan 额度测试 新版本libatapp的连接管理——从etcd服务发现到拓扑驱动的自动重连 新版本libatbus的设计变更——从树形路由到拓扑驱动 Protobuf又一坑 - C++标准和ABI兼容性 AI真好用-给Blog主题统一加mermaid,chart.js,excalidraw,draw.io的多种引入方式支持 给内网部署Squid-通用HTTP下载缓存 UE使用CodeChecker和clang-tidy生成静态分析报告 找出UE的循环依赖 C++小协程栈和临时变量及作用域的栈溢出问题分析 游戏服务的可观测性能力建设(C++生态) 指标上报的多线程优化和多拉取源点优化 协程(libcopp)的Channel功能和CPU命中率优化 通用RPC代码生成器 实现strong_rc_ptr(比shared_ptr更快的引用计数智能指针) 手夯一个STL allocator和对象内存分析组件 std::condition_variable 的信号丢失问题 踩坑一处(GCC)STL std::async 实现BUG导致的crash问题 GCC 14的一个warning to error BUG 给xresloader(Excel导表工具)增强UE读表支持(包含蓝图,Blueprint) Opentelemetry社区在gRPC的几个链接问题(静态库和动态库混用,musl工具链,符号裁剪) Excel转表工具(xresloader)的新验证器(验证外部Excel和文本数据,唯一性和自定义规则) protobuf v22和gRPC v1.55版本升级的依赖变化和upb适配 关于protobuf近期版本(v20/v3.20+)和 gRPC v1.54版本在某些编译环境下的一些链接和编译问题 xresloader-Excel导表工具链的近期变更汇总 打通游戏服务端框架的C++20协程改造的最后一环 Opentelemetry-cpp的Logs模块标准更新(涉及近期版本:1.8-1.9的BREAK CHANGES) 给cmake-toolset和工具链(curl等)加HTTP/2和HTTP/3支持 又开新坑之 coredns 插件: nftables和filter 填个转表工具 xresloader 去年的坑(数组尾部裁剪) 集成 upb 和 lua binding 的踩坑小记 libcopp对C++20协程的接入和接口设计 再度优化GCC、LLVM、Clang、libc++、libc++abi等套件的构建脚本 游戏服务的分布式事务优化(二)- 事务管理 游戏服务的分布式事务优化(一)- Write Ahead Log(WAL) 模块 记录一些bazel适配用编译选项 测试现代化硬件C++浮点数性能和一致性 适配Boringssl和OpenSSL 3.0 近期cmake-toolset的一些适配问题 C++20 Text Formatting/fmtlib 适配问题小记 再次重构LLVM+Clang+libcxx+libc++abi+其他相关工具的构建流程 重构基于CMake的构建工具链 新版GCC和LLVM+Clang终于Release啦 折腾一下nftables下的双拨 [C++20] Module partitions和符号交叉引用(声明和实现分离) [Rust] 实现一个线程安全且迭代器可以保存的链表 基于protobuf的代码生成 几个使用protobuf中C++接口的Arena的坑 Amazon Aurora DB存储引擎论文阅读小记 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等) xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等) 在游戏服务器中使用分布式事务 libcopp接入C++20 Coroutine和一些过渡期的设计 libatbus 的大幅优化 nftables初体验 容器配置开发环境小计 PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记 跨平台协程库 - libcopp 简介 C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比) 尝鲜Github Action 一些xresloader(转表工具)的改进 protobuf、flatbuffer、msgpack 针对小数据包的简单对比 协程框架(libcopp) 小幅优化 Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持 Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记 C++20 Coroutine libcopp merge boost.context 1.69.0 Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感 Rust玩具-企业微信机器人通用服务 使用ELK辅助监控开发测试环境服务质量和问题定位 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现 Webpack+vue+boostrap+ejs构建Web版GM工具 Rust的第二次接触-写个小服务器程序 理解和适配AEAD加密套件 atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比 可执行文件压缩 初识Rust 使用restructedtext编写xresloader文档 atframework的etcd模块化重构 C++的backtrace ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装 protobuf-net的动态Message实现 pbc的proto3接入 atgateway内置协议流程优化-加密、算法协商和ECDH 整理一波软件源镜像同步工具+DevOps工具 Blog切换到Hugo libcopp v2的第一波优化完成 libcopp(v2) vs goroutine性能测试 libcopp的线程安全、栈池和merge boost.context 1.64.0 GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本 libatbus的几个藏得很深的bug 用cmake交叉编译到iOS和Android 开源项目得一些小维护 atapp的c binding和c#适配 对象路由系统设计 2016年总结 近期的一个协程流程BUG 重写了llvm+clang+libc++和libc++abi的构建脚本 atsf4g完整游戏工程示例|I'm OWenT atframework基本框架已经完成|I'm OWenT
关于opentelemetry-cpp社区对于C++ Head Only组件单例和符号可见性的讨论小记
owent · 2022-10-03 · via I'm OWenT

blog-website

前言

前段时间有人在 opentelemetry-cpp 提出了api组件在动态库中单例无法工作的 issue ,( https://github.com/open-telemetry/opentelemetry-cpp/issues/1520 ) 。

opentelemetry 是可观测性领域的开源项目,目标是统一链路跟踪、数据指标和日志的服务、上报、协议和接口规范,目前属于 CNCF基金会 孵化项目。而 opentelemetry-cpp 则是 opentelemetry 中对标准规范SDK的C++实现。

其本质原因是 opentelemetry-cpp 规范要求api组件必须是header-only的( https://github.com/open-telemetry/opentelemetry-cpp/blob/main/docs/requirements.md#requirements )。

opentelemetry-cpp 一直以来仅仅提供了跨平台的静态库支持,对动态库并没有找到一个特别理想的方式。这里记录一下当时整个讨论的要点记录。

背景

三大主流平台(macOS,Linux和Windows )中用的ABI规范都不太一样。其中macOS的Mach-O和Linux下的ELF差别不是很大,但是它们Windows下PE ABI的差异很大。 对动态库而言,实际上所有写在 header 里的接口和全局变量都有一份自己的实例。而关键性的差异主要是 ELF和Mach-O 对于 -fvisibility=default 的变量都是去GOT查找,而如果GOT找不到会走到第一次调用的模块的初始化流程,然后初始化完以后会把这个地址写入GOT,GOT是所有模块共享的,函数符号表也是共享的。

另外,虽然 -fvisibility=default 是可见性设置的默认值,但是有些下游库为了版本兼容性和符号隔离,会在编译选项中加入 -fvisibility=hidden 来使得符号是默认隐藏的。这种情况又不太一样,各个模块会直接使用本地的函数,不再从GOT中查找。导致各个模块最终使用的同名变量和函数地址不一样。

而在PE中,dll调用函数,除非标记为 __declspec(dllimport),总是从本模块的符号表总查找。而标记为 __declspec(dllimport) 的函数不会生成本地实例,所以会要求必须要有某个模块申明了 __declspec(dllexport) 来导出实际的实例。每个dll和exe都有自己独立符号表和堆管理。

介于以上原因,大多数 C/C++ 库的解决方式是在声明接口时加一个宏 XXX_API ,然后不同的场景通过宏来切换到不同的编译分支,比如:

// ================ import/export: for compilers ================
#if defined(__GNUC__) && !defined(__ibmxl__)
//  GNU C++/Clang
//
// Dynamic shared object (DSO) and dynamic-link library (DLL) support
//
#  if __GNUC__ >= 4
#    if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__)
// All Win32 development environments, including 64-bit Windows and MinGW, define
// _WIN32 or one of its variant spellings. Note that Cygwin is a POSIX environment,
// so does not define _WIN32 or its variants.
#      ifndef OPENTELEMETRY_SYMBOL_EXPORT
#        define OPENTELEMETRY_SYMBOL_EXPORT __attribute__((__dllexport__))
#      endif
#      ifndef OPENTELEMETRY_SYMBOL_IMPORT
#        define OPENTELEMETRY_SYMBOL_IMPORT __attribute__((__dllimport__))
#      endif
#    else
#      ifndef OPENTELEMETRY_SYMBOL_EXPORT
#        define OPENTELEMETRY_SYMBOL_EXPORT __attribute__((visibility("default")))
#      endif
#      ifndef OPENTELEMETRY_SYMBOL_IMPORT
#        define OPENTELEMETRY_SYMBOL_IMPORT __attribute__((visibility("default")))
#      endif
#    endif
#  else
// config/platform/win32.hpp will define OPENTELEMETRY_SYMBOL_EXPORT, etc., unless already defined
#    ifndef OPENTELEMETRY_SYMBOL_EXPORT
#      define OPENTELEMETRY_SYMBOL_EXPORT
#    endif
#    ifndef OPENTELEMETRY_SYMBOL_IMPORT
#      define OPENTELEMETRY_SYMBOL_IMPORT
#    endif
#  endif
#elif defined(_MSC_VER)
//  Microsoft Visual C++
//
//  Must remain the last #elif since some other vendors (Metrowerks, for
//  example) also #define _MSC_VER
#else
#endif
// ---------------- import/export: for compilers ----------------

// ================ import/export: for platform ================
//  Default defines for OPENTELEMETRY_SYMBOL_EXPORT and OPENTELEMETRY_SYMBOL_IMPORT
//  If a compiler doesn't support __declspec(dllexport)/__declspec(dllimport),
//  its file must define OPENTELEMETRY_SYMBOL_EXPORT and OPENTELEMETRY_SYMBOL_IMPORT
#if !defined(OPENTELEMETRY_SYMBOL_EXPORT) && (defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__))

#  ifndef OPENTELEMETRY_SYMBOL_EXPORT
#    define OPENTELEMETRY_SYMBOL_EXPORT __declspec(dllexport)
#  endif
#  ifndef OPENTELEMETRY_SYMBOL_IMPORT
#    define OPENTELEMETRY_SYMBOL_IMPORT __declspec(dllimport)
#  endif
#endif
// ---------------- import/export: for platform ----------------

#ifndef OPENTELEMETRY_SYMBOL_EXPORT
#  define OPENTELEMETRY_SYMBOL_EXPORT
#endif
#ifndef OPENTELEMETRY_SYMBOL_IMPORT
#  define OPENTELEMETRY_SYMBOL_IMPORT
#endif
// ---------------- import/export ----------------

// private definition for both static and dynamic library.
#define OPENTELEMETRY_API_API_NATIVE

// public definition for dynamic library only.
#define OPENTELEMETRY_API_API_DLL

// And then declare these.
#if defined(OPENTELEMETRY_API_API_NATIVE) && OPENTELEMETRY_API_API_NATIVE
#  if defined(OPENTELEMETRY_API_API_DLL) && OPENTELEMETRY_API_API_DLL
#    define OPENTELEMETRY_API_API OPENTELEMETRY_SYMBOL_EXPORT
#  else
#    define OPENTELEMETRY_API_API
#  endif
#else
#  if defined(OPENTELEMETRY_API_API_DLL) && OPENTELEMETRY_API_API_DLL
#    define OPENTELEMETRY_API_API OPENTELEMETRY_SYMBOL_IMPORT
#  else
#    define OPENTELEMETRY_API_API
#  endif
#endif
#define OPENTELEMETRY_API_API_HEAD_ONLY OPENTELEMETRY_SYMBOL_VISIBLE

详见: https://github.com/open-telemetry/opentelemetry-cpp/issues/1105

在Linux和macOS中,符号设置为 __attribute__((visibility("default"))) 之后,对某个名字的函数调用总会找到同一个函数地址(除非 dlopen+dlsym 然后显式按地址调用)。 在Windows下,有且只能有一个模块的代码实现把 OPENTELEMETRY_SYMBOL_EXPORT 声明为 __declspec(dllexport) ,其他模块声明为 __declspec(import) 。 只有一个模块有实现,其他的模块都是导入这个模块的实现,否则链接时会报重定义。所以这个申明必须位于 cpp 文件中,不能位于头文件中。这就和前面提到的 opentelemetry-cpp 规范要求 api组件必须是header-only的 相冲突。而但凡写在头文件里。就会导致不同的模块对 单例 对象的引用,使用不同的变量地址,从而失去了 单例 的意义。

几个编译环境的example

在issue讨论中,首先对于Linux/macOS,可以显式把涉及单例的接口设置为 __attribute__((visibility("default"), weak)) ,这样不受全局选项的影响。而对于Windows,有人提到了在MSVC中可以用 __declspec(selectany) 代替 __attribute__((visibility("default"), weak)) 。看 __declspec( selectany ) 的文档,似乎确实是一个解决方案。同时在Windows下的 GCC/Clang 中,也有对应的选项 __attribute__((selectany)) (注意 selectany 只能用于变量,不能用于函数)。所以我写了几个Windows下的example用于测试可行性。

Case 1: Windows+GCC+visibility+static local变量

文件: test_dll.h

#pragma once

#include <iostream>

struct foo {
  __attribute__((visibility("default"))) static void print_addr() {
    __attribute__((visibility("default"))) static foo inst;
    inst.print();
    std::cout<< "print_addr address: "<< foo::print_addr<< std::endl;
  }

  void print() {
    std::cout<< "Instance address: "<< this<< std::endl;
  }
};

文件: test_dll.cpp

#include "test_dll.h"

#include <iostream>

__attribute__((dllexport)) void call_print() {
  foo::print_addr();
}

文件: test_main.cpp

#include "test_dll.h"

__attribute__ ((dllimport)) void call_print();

int main() {
  foo::print_addr();
  call_print();
  return 0;
}

编译指令和执行结果:

$ g++ test_dll.cpp -shared -o test_dll.dll
In file included from test_dll.cpp:1:
test_dll.h: In static member function 'static void foo::print_addr()':
test_dll.h:7:56: warning: 'visibility' attribute ignored [-Wattributes]
    7 |     __attribute__((visibility("default"))) static foo inst;
      |                                                       ^~~~

$ g++ test_main.cpp -o test_dll.exe -L. -ltest_dll
In file included from test_main.cpp:1:
test_dll.h: In static member function 'static void foo::print_addr()':
test_dll.h:7:56: warning: 'visibility' attribute ignored [-Wattributes]
    7 |     __attribute__((visibility("default"))) static foo inst;
      |                                                       ^~~~


$ ./test_dll.exe
Instance address: 0x22e66cb1410
print_addr address: 1
Instance address: 0x22e66b834a0
print_addr address: 1

$ cat /etc/os-release
NAME=MSYS2
ID=msys2
PRETTY_NAME="MSYS2"
ID_LIKE="cygwin arch"
HOME_URL="https://www.msys2.org"
BUG_REPORT_URL="https://github.com/msys2/MSYS2-packages/issues"

$ gcc -v
Using built-in specs.
COLLECT_GCC=C:\msys64\mingw64\bin\gcc.exe
COLLECT_LTO_WRAPPER=C:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../gcc-12.1.0/configure --prefix=/mingw64 --with-local-prefix=/mingw64/local --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --with-native-system-header-dir=/mingw64/include --libexecdir=/mingw64/lib --enable-bootstrap --enable-checking=release --with-arch=x86-64 --with-tune=generic --enable-languages=c,lto,c++,fortran,ada,objc,obj-c++,jit --enable-shared --enable-static --enable-libatomic --enable-threads=posix --enable-graphite --enable-fully-dynamic-string --enable-libstdcxx-filesystem-ts --enable-libstdcxx-time --disable-libstdcxx-pch --enable-lto --enable-libgomp --disable-multilib --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-libiconv --with-system-zlib --with-gmp=/mingw64 --with-mpfr=/mingw64 --with-mpc=/mingw64 --with-isl=/mingw64 --with-pkgversion='Rev2, Built by MSYS2 project' --with-bugurl=https://github.com/msys2/MINGW-packages/issues --with-gnu-as --with-gnu-ld --disable-libstdcxx-debug --with-boot-ldflags=-static-libstdc++ --with-stage1-ldflags=-static-libstdc++
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 12.1.0 (Rev2, Built by MSYS2 project)

显然,两个模块内地址不一致。

Case 2: Windows+GCC+visibility+全局变量

文件: test_dll.h

#pragma once

#include <iostream>

struct foo {
  __attribute__((visibility("default"))) static void print_addr() {
    inst.print();
    std::cout<< "print_addr address: "<< foo::print_addr<< std::endl;
  }

  void print() {
    std::cout<< "Instance address: "<< this<< std::endl;
  }

  __attribute__((weak, visibility("default"))) static foo inst;
};


__attribute__((weak, visibility("default"))) foo foo::inst;

其他文件同 Case 1

编译指令和执行结果:

$ ./test_dll.exe
Instance address: 0x7ff7750f7040
print_addr address: 1
Instance address: 0x7ff87b247020
print_addr address: 1

显然,两个模块内地址也不一致。

Case 3: Windows+MSVC+__declspec(selectany)

文件: test_dll.h

#include "test_dll.h"

#include <iostream>

__declspec(dllexport) void call_print() {
  foo::print_addr();
}

文件: test_dll.cpp

#include "test_dll.h"

#include <iostream>

__declspec(dllexport) void call_print() {
  foo::print_addr();
}

文件: test_main.cpp

#include "test_dll.h"

__declspec(dllimport) void call_print();

int main() {
    foo::print_addr();
    call_print();
    return 0;
}

编译指令和执行结果(直接链接 .dll):

cl /nologo /O2 /MD /Zi /Z7 /LD test_dll.cpp
cl /nologo /O2 /MD /Zi /Z7 test_main.cpp /link test_dll

d:\workspace\test\vcconsole\testdll>test_main.exe
Instance address: 00007FF6551F9150
print_addr address: 00007FF6551F1140
Instance address: 00007FF6551F9150
print_addr address: 00007FF6551F1140

这种方法和我们预计的结果一致,单例的地址唯一了。但是对于所有的dll,MSVC还是生成一个用于 .lib 文件也是用于链接的,像cmake这类构建工具,只会链接这个.lib文件,而不是直接链接 .dll 。

我们再来试一下链接.dll对应的.lib:

cl /nologo /O2 /MD /Zi /Z7 /LD test_dll.cpp
cl /nologo /O2 /MD /Zi /Z7 test_main.cpp /link test_dll.lib

d:\workspace\test\vcconsole\testdll>test_main.exe
Instance address: 00E08130
print_addr address: 00E0114A
Instance address: 796A8120
print_addr address: 796A1122

这种方式地址又不一样了。

Case 4: Windows+GCC+__attribute__((selectany))

文件: test_dll.h

#pragma once

#include <iostream>

struct foo {
  __attribute__((visibility("default"))) static void print_addr() {
    inst.print();
    std::cout<< "print_addr address: "<< foo::print_addr<< std::endl;
  }

  void print() {
    std::cout<< "Instance address: "<< this<< std::endl;
  }

  __attribute__((visibility("default"), weak)) static foo inst;
};

__attribute__((visibility("default"), weak, selectany)) foo foo::inst;

其他文件同 Case 1

编译指令和执行结果:

$ ./test_dll.exe
Instance address: 0x7ff735ac30b0
print_addr address: 1
Instance address: 0x7ff8d19c3070
print_addr address: 1

显然,结果仍然是地址不一致。

ELF的特例(global变量和static local变量的差异)

这里顺便提及一下Linux下全局变量和函数内static变量的差异。macOS我没深入研究过不过估计结论应该类似。

首先global变量是模块加载时自动初始化,初始化顺序不定。而函数内static变量是在第一次访问时初始化。 虽然C++ 11规定函数内static变量的初始化必须是线程安全的,但是GCC和Clang都是通过一个atomic操作去判定是否初始化的。并且 acquire 操作, 复制操作, release 操作是三条指令。 仍然有可能多线程同时进入。

而global变量首先的问题是初始化顺序不确定,导致如果多个组件互相依赖的话初始化顺序也是不定的。

另一个更大的问题是,对全局变量的初始化会直接写进模块的初始化函数,这会导致如果多个模块引用同名全局变量,虽然符号地址走GOT是统一了,但是构造和析构函数会执行多次。 这会带来严重问题,细节可以参考 https://godbolt.org/z/7aTTh939c 这个输出的汇编。

这里也有一个和这个有关的 issue https://github.com/open-telemetry/opentelemetry-cpp/issues/1603 。 问题根源是 gRPC 里有这样的全局变量,当以静态库编译 gRPC ,动态库编译 opentelemetry-cpp 时, gRPC 的相关全局变量被多次初始化,导致部分数据被覆盖。 解决方法就是增加了一个 opentelemetry-cpp::otlp_grpc_client 模块,有且仅有这个模块链接 gRPC 。 相关PR是 https://github.com/open-telemetry/opentelemetry-cpp/pull/1606 。

最后

通过上面的例子,Linux和macOS下我们可以通过一定的约定和规范避免问题,但是Windows下仍然没有一个完美的解决方案。

当然,有的同学说,我只用Linux/macOS,不用考虑Windows。然而我们做基础性功能库的时候不能定死用户的使用场景和限制使用平台,所以对接口的规范和约定设计会更加偏向保守。 当前版本的 opentelemetry-cpp 中,已经把单例中GCC和Clang编译时符号设置为 __attribute__((visibility("default"), weak)) 。 这样头文件里的函数和变量可见性不受全局编译选项的影响,总是共享。于此同时我们必须注意ABI兼容性,因为无法在通过 -fvisibility=hidden 个隔离多版本。 在 opentelemetry-cpp 中还通过 inline namespace 机制来隔离多版本的ABI兼容性(仅限于stable接口)。

而对于Windows,目前还是仅支持编译成静态库,但是可以链接到动态库中且多个模块间互不影响。

欢迎有兴趣的小伙伴们交流。