跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组
折叠
品牌标识

D2Learn Forums

woshinideba1425W

woshinideba1425

@woshinideba1425
关于
帖子
2
主题
1
群组
0
粉丝
0
关注
0

帖子

最新 最佳 有争议的

  • C++ 模块中静态全局变量的消失问题
    woshinideba1425W woshinideba1425

    错误例子(我写的)

    module;
    
    #include "httplib.h"
    
    export module mcp.compat.httplib;
    
    export namespace mcp {
        using HttpClient = httplib::Client;
        using DataSink = httplib::DataSink;
        using Headers = httplib::Headers;
        using HttpRequest = httplib::Request;
        using HttpResponse = httplib::Response;
        using HttpServer = httplib::Server;
    
        #ifdef MCP_SSL
        using SslServer = httplib::SSLServer;
        #endif
    }
    

    正确例子(官方库提供的)

    module;
    
    /*
     * Headers
     */
    
    #ifdef _WIN32
    #ifndef _CRT_SECURE_NO_WARNINGS
    #define _CRT_SECURE_NO_WARNINGS
    #endif //_CRT_SECURE_NO_WARNINGS
    
    #ifndef _CRT_NONSTDC_NO_DEPRECATE
    #define _CRT_NONSTDC_NO_DEPRECATE
    #endif //_CRT_NONSTDC_NO_DEPRECATE
    
    #if defined(_MSC_VER)
    #if _MSC_VER < 1900
    #error Sorry, Visual Studio versions prior to 2015 are not supported
    #endif
    
    #pragma comment(lib, "ws2_32.lib")
    
    #ifndef _SSIZE_T_DEFINED
    #define _SSIZE_T_DEFINED
    #endif
    #endif // _MSC_VER
    
    #ifndef S_ISREG
    #define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
    #endif // S_ISREG
    
    #ifndef S_ISDIR
    #define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
    #endif // S_ISDIR
    
    #ifndef NOMINMAX
    #define NOMINMAX
    #endif // NOMINMAX
    
    #include <io.h>
    #include <winsock2.h>
    #include <ws2tcpip.h>
    
    #if defined(__has_include)
    #if __has_include(<afunix.h>)
    // afunix.h uses types declared in winsock2.h, so has to be included after it.
    #include <afunix.h>
    #define CPPHTTPLIB_HAVE_AFUNIX_H 1
    #endif
    #endif
    
    #ifndef WSA_FLAG_NO_HANDLE_INHERIT
    #define WSA_FLAG_NO_HANDLE_INHERIT 0x80
    #endif
    
    
    #else // not _WIN32
    
    #include <arpa/inet.h>
    #if !defined(_AIX) && !defined(__MVS__)
    #include <ifaddrs.h>
    #endif
    #ifdef __MVS__
    #include <strings.h>
    #ifndef NI_MAXHOST
    #define NI_MAXHOST 1025
    #endif
    #endif
    #include <net/if.h>
    #include <netdb.h>
    #include <netinet/in.h>
    #ifdef __linux__
    #include <resolv.h>
    #undef _res // Undefine _res macro to avoid conflicts with user code (#2278)
    #endif
    #include <csignal>
    #include <netinet/tcp.h>
    #include <poll.h>
    #include <pthread.h>
    #include <sys/mman.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <unistd.h>
    
    #ifndef INVALID_SOCKET
    #define INVALID_SOCKET (-1)
    #endif
    #endif //_WIN32
    
    #if defined(__APPLE__)
    #include <TargetConditionals.h>
    #endif
    
    #include <algorithm>
    #include <array>
    #include <atomic>
    #include <cassert>
    #include <cctype>
    #include <chrono>
    #include <climits>
    #include <condition_variable>
    #include <cstdlib>
    #include <cstring>
    #include <errno.h>
    #include <exception>
    #include <fcntl.h>
    #include <functional>
    #include <iomanip>
    #include <iostream>
    #include <list>
    #include <map>
    #include <memory>
    #include <mutex>
    #include <random>
    #include <regex>
    #include <set>
    #include <sstream>
    #include <string>
    #include <sys/stat.h>
    #include <system_error>
    #include <thread>
    #include <unordered_map>
    #include <unordered_set>
    #include <utility>
    
    #if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) ||                        \
        defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
    #if TARGET_OS_MAC
    #include <CFNetwork/CFHost.h>
    #include <CoreFoundation/CoreFoundation.h>
    #endif
    #endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or
           // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
    
    #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
    #ifdef _WIN32
    #include <wincrypt.h>
    
    // these are defined in wincrypt.h and it breaks compilation if BoringSSL is
    // used
    #undef X509_NAME
    #undef X509_CERT_PAIR
    #undef X509_EXTENSIONS
    #undef PKCS7_SIGNER_INFO
    
    #ifdef _MSC_VER
    #pragma comment(lib, "crypt32.lib")
    #endif
    #endif // _WIN32
    
    #if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
    #if TARGET_OS_MAC
    #include <Security/Security.h>
    #endif
    #endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
    
    #include <openssl/err.h>
    #include <openssl/evp.h>
    #include <openssl/ssl.h>
    #include <openssl/x509v3.h>
    
    #if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
    #include <openssl/applink.c>
    #endif
    
    #include <iostream>
    #include <sstream>
    
    #if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
    #if OPENSSL_VERSION_NUMBER < 0x1010107f
    #error Please use OpenSSL or a current version of BoringSSL
    #endif
    #define SSL_get1_peer_certificate SSL_get_peer_certificate
    #elif OPENSSL_VERSION_NUMBER < 0x30000000L
    #error Sorry, OpenSSL versions prior to 3.0.0 are not supported
    #endif
    
    #endif // CPPHTTPLIB_OPENSSL_SUPPORT
    
    #ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
    #include <mbedtls/ctr_drbg.h>
    #include <mbedtls/entropy.h>
    #include <mbedtls/error.h>
    #include <mbedtls/md5.h>
    #include <mbedtls/net_sockets.h>
    #include <mbedtls/oid.h>
    #include <mbedtls/pk.h>
    #include <mbedtls/sha1.h>
    #include <mbedtls/sha256.h>
    #include <mbedtls/sha512.h>
    #include <mbedtls/ssl.h>
    #include <mbedtls/x509_crt.h>
    #ifdef _WIN32
    #include <wincrypt.h>
    #ifdef _MSC_VER
    #pragma comment(lib, "crypt32.lib")
    #endif
    #endif // _WIN32
    #if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
    #if TARGET_OS_MAC
    #include <Security/Security.h>
    #endif
    #endif // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
    
    // Mbed TLS 3.x API compatibility
    #if MBEDTLS_VERSION_MAJOR >= 3
    #define CPPHTTPLIB_MBEDTLS_V3
    #endif
    
    #endif // CPPHTTPLIB_MBEDTLS_SUPPORT
    
    // Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available
    // This simplifies conditional compilation when adding new backends (e.g.,
    // wolfSSL)
    #if defined(CPPHTTPLIB_OPENSSL_SUPPORT) || defined(CPPHTTPLIB_MBEDTLS_SUPPORT)
    #define CPPHTTPLIB_SSL_ENABLED
    #endif
    
    #ifdef CPPHTTPLIB_ZLIB_SUPPORT
    #include <zlib.h>
    #endif
    
    #ifdef CPPHTTPLIB_BROTLI_SUPPORT
    #include <brotli/decode.h>
    #include <brotli/encode.h>
    #endif
    
    #ifdef CPPHTTPLIB_ZSTD_SUPPORT
    #include <zstd.h>
    #endif
    
    
    export module httplib;
    
    export extern "C++" {
        #include "httplib.h"
    }
    
    

    当你试图将一个非模块化的第三方库(尤其是像 httplib 这种带全局状态或初始化逻辑的库)封装进模块时:

    不要尝试只导出部分类型:除非你非常确定该库没有全局初始化逻辑(Constructor static guards)。

    使用 export extern "C++":这是将传统头文件“模块化”的最标准、最稳妥做法。

    环境对齐:在 module; 之后,务必把该库依赖的所有系统宏(如 _WIN32, WIN32_LEAN_AND_MEAN 等)都写清楚。


  • C++ 模块中静态全局变量的消失问题
    woshinideba1425W woshinideba1425

    1. 问题现象描述

    在 Windows 平台上,使用 cpp-httplib 等依赖 WinSock 的库时,若将其封装在 C++ 模块中,常会出现网络初始化失败的问题。根本原因在于 httplib.h 内部用于自动调用 WSAStartup 的静态守卫对象 static WSInit wsinit_; 在编译产物中消失了,导致构造函数未触发。
    f7696011-2b2a-4f44-a2ce-402478b69d33-image.png

    2. 核心原理分析

    2.1 全局模块片段(Global Module Fragment, GMF)

    在模块文件中,位于 module; 和 export module 之间的代码属于 GMF。其设计初衷是为了包含那些尚未模块化的传统头文件,同时防止头文件中的宏和私有符号污染模块外部。

    2.2 丢弃规则与可达性(Discarding & Reachability)

    根据 C++ 标准,编译器对 GMF 的处理遵循**按需保留(Discarding)**原则。

    • 原理:只有当 GMF 中的声明被模块体(Module Body)显式引用或**导出(Export)**时,该声明才会进入最终的二进制模块接口(BMI)。
    • 逻辑:如果一个定义在 GMF 中的符号(变量、函数、类)在模块的 export 部分或逻辑实现中完全没被用到,编译器会认为它是该模块的“实现细节”且“对模块接口无贡献”,从而在 BMI 生成阶段将其彻底剔除。

    2.3 静态变量的内部链接属性(Internal Linkage)

    static 修饰的变量具有内部链接属性。

    • 在传统 .cpp 文件中,即使不引用该变量,由于它是翻译单元的一部分,链接器通常会保留它。
    • 在 Modules 机制下,编译器在构建模块接口时具有更强的静态分析能力。由于 static WSInit wsinit_ 被限制在当前作用域,且模块体中没有任何代码通过名称访问它,编译器会判定该变量为 Dead Code。

    3. 标准规范依据

    根据 C++20 标准 (ISO/IEC 14882:2020) 以及后续 C++23 修订:

    [module.global.frag] p4:
    "A declaration in the global module fragment of a module unit is discarded if it does not appear in the residue of the respective module unit."

    [module.reach] 可达性规定:
    只有当一个声明是“可达的”(Reachable)时,它才会在翻译单元中生效。对于 GMF 里的声明,除非它被模块内的声明直接或间接“使用”(Used),否则它被视为不可达并被丢弃。

    结论:编译器这样做是为了确保生成的 BMI 文件尽可能小,并严格控制符号的可见性。副作用是依赖全局对象构造函数执行的“自动初始化”逻辑会失效。


    4. 解决方案对比

    方案 描述 评价
    显式引用 在模块导出函数或类中强行读取一下 wsinit_ 不推荐。代码丑陋,且可能被激进的优化器再次优化掉。
    手动初始化 将 WSAStartup 逻辑封装为导出的 init() 函数 推荐。符合现代 C++ 显式优于隐式的原则。
    包装为局部静态 在导出的类构造函数或单例中使用 static 局部变量 最佳实践。利用“Magic Static”保证线程安全且绝不会被丢弃。

    5. 最佳实践示例

    在封装类似库时,建议采用以下结构:

    // mcp_network.cppm
    module;
    #include "httplib.h"
    
    export module mcp.network;
    
    export namespace mcp {
        class NetworkProvider {
        public:
            NetworkProvider() {
                // 将初始化逻辑绑定到实际会被导出的类型上
                #ifdef _WIN32
                static struct WinSockInit {
                    WinSockInit() {
                        WSADATA wsa;
                        WSAStartup(MAKEWORD(2,2), &wsa);
                    }
                    ~WinSockInit() { WSACleanup(); }
                } global_init;
                #endif
            }
        };
    }
    
    

    如果引用出错或者总结有问题,请提出

  • 登录

  • 没有帐号? 注册

  • 登录或注册以进行搜索。
d2learn forums Powered by NodeBB
  • 第一个帖子
    最后一个帖子
0
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组