【问题标题】:why static inline function with static variable behave so differently?为什么带有静态变量的静态内联函数的行为如此不同?
【发布时间】:2021-06-24 19:44:12
【问题描述】:

我很难理解为什么编译器和平台之间的行为会有如此大的差异。

这是http://kyungminlee.org/doc/minutiae/local_static_variable_shared_library.html 的扩展example

CMakeLists.txt:

cmake_minimum_required(VERSION 3.7.2)
project(static_inline)

set(CMAKE_CXX_STANDARD 14)

add_library(lib SHARED collect.h collect.cpp)
target_compile_definitions(lib PRIVATE BUILD_DLL)

add_executable(static_inline main.cpp)
target_link_libraries(static_inline PRIVATE lib)

enable_testing()
add_test(NAME test.static_inline
        COMMAND static_inline)

收集.h

#pragma once

#ifdef _WIN32
#   define STATIC
#   ifdef BUILD_DLL
#       define EXPORT __declspec(dllexport)
#   else
#       define EXPORT __declspec(dllimport)
#   endif
#else
#   define STATIC static
#   define EXPORT
#endif

// static will not compile on windows since static inline has internal linkage
EXPORT STATIC inline int collect(int x)
{
    static int sum = 0;
    sum += x;
    return sum;
}
EXPORT int get_sum();

struct EXPORT foo
{
    static inline int collect(int x)
    {
        static int sum = 0;
        sum += x;
        return sum;
    }
    int get_sum();
};

收集.cpp

#include "collect.h"

int get_sum()
{
    return collect(0);
}

int foo::get_sum()
{
    return collect(0);
}

main.cpp

#include "collect.h"

#include <iostream>

int main()
{
    int num_from_inline_function = collect(10);
    int num_from_inline_function2 = get_sum();
    std::cout << "static inline collect: " << num_from_inline_function << std::endl;
    std::cout << "get_sum: " << num_from_inline_function2 << std::endl;

    int num_from_inline_static_member_function = foo::collect(10);
    int num_from_inline_static_member_function2 = foo().get_sum();
    std::cout << "static inline foo::collect: " << num_from_inline_static_member_function << std::endl;
    std::cout << "foo::get_sum: " << num_from_inline_static_member_function2 << std::endl;

    return !(num_from_inline_static_member_function == num_from_inline_static_member_function2 &&
            num_from_inline_function == num_from_inline_static_member_function2);
}

Windows 输出:

MinGW-w64.

1: Test command: C:\dev\repos\static_inline\cmake-build-release-mingw-w64\static_inline.exe
1: Test timeout computed to be: 10000000
1: static inline collect: 10
1: get_sum: 0
1: static inline foo::collect: 10
1: foo::get_sum: 0
Failed

叮当

1: Test command: C:\dev\repos\static_inline\cmake-build-release-mingw-w64\static_inline.exe
1: Test timeout computed to be: 10000000
1: static inline collect: 10
1: get_sum: 0
1: static inline foo::collect: 10
1: foo::get_sum: 0
Failed

MSVC

1: Test command: C:\dev\repos\static_inline\cmake-build-release-visual-studio\static_inline.exe
1: Test timeout computed to be: 10000000
1: static inline collect: 10
1: get_sum: 10
1: static inline foo::collect: 10
1: foo::get_sum: 10

100% tests passed, 0 tests failed out of 1

Ubuntu

GCC 和 Clang 给出相同的输出

1: Test command: /home/travis/build/ElDesalmado/static_inline_example/build/static_inline
1: Test timeout computed to be: 9.99988e+06
1: static inline collect: 10
1: get_sum: 0
1: static inline foo::collect: 10
1: foo::get_sum: 10
1/1 Test #1: test.static_inline ...............   Passed    0.00 sec
100% tests passed, 0 tests failed out of 1

结果仅在 Ubuntu 上看起来有些一致。在 Windows 上,只有 MSVC 的行为有所不同。 MSVC 不仅与 MinGW 和 Clang 不同,而且与 Ubuntu 上的 GCC 和 Clang 不同。

我想对于 Windows 上的 MSVC,我们看到的结果是因为链接器删除了内联函数和成员函数的重复符号:

  1. 标准是否要求链接器删除内联函数的重复符号?
    对于内联函数,本地静态成员(我记得)保证在同一个地址:

同一个内联函数(可能是隐式内联)的所有定义中的函数局部静态对象都引用一个翻译单元中定义的同一个对象。
2. 带有函数局部静态对象的静态成员内联函数怎么样?它们是否在 个翻译单元中引用相同的对象?

  • Windows 上的 MSVC 显然是 YES。这种行为在编译中是否一致?
  • Windows 上的 MinGW 和 Clang 说 NO
  • Ubuntu 上的 GCC 和 Clang 说 YES
  • 标准对此有何规定?
  1. 为加载插件的库或核心应用程序依赖静态内联函数和静态内联成员函数对 ABI 的友好程度如何?例如,如果类型 id 存储为函数本地静态内联静态函数/内联静态成员函数中的对象?
    • 例如,我知道对内联函数体的任何更改都会破坏 ABI。但除此之外呢?
class __declspec(dllexport) counter
{
   static int get() // implicitly inline
   {
      static int current = 0;
      return current++;
   }
};

【问题讨论】:

  • C++ 标准知道动态库的存在;因此,一旦您使用一个,您就会进入实现定义的行为。
  • (4) 无法回答(就这个问题而言),因为它与 [language-lawyer] 标签冲突;也就是说,动态库不是标准的一部分。
  • @RichardCritten C 标准有An inline definition of a function with external linkage shall not contain a definition of a modifiable object with static or thread storage duration, and shall not contain a reference to an identifier with internal linkage. C++ 标准对此有什么说法吗?这并没有说明动态库。
  • 在这里阅读 解释 en.cppreference.com/w/cpp/language/inline 它似乎没有相同的限制。当我们得到内联变量时(不确定这是否有影响)只是试图限制范围
  • @RichardCritten 从那个解释看来,在 Windows 上只有 MSVC 符合标准,即Function-local static objects in all function definitions are shared across all translation units (they all refer to the same object defined in one translation unit)。然而在 Ubuntu 上却相反,看起来虽然一致但依赖于实现。

标签: c++ linker language-lawyer cross-platform


【解决方案1】:

对于#define STATIC staticmain 调用的::collect::get_sum 调用的函数只是一个不同的函数,所以我不知道你期望的其他行为。这不适用于 member 函数,无论是静态的还是其他的; static 在这里意味着完全不同的东西,foo 的多个定义都定义了相同的 type 和相同的成员函数。

【讨论】:

  • 虽然 ::collect(...)::get_sum() 是这样,但这并不能完全解释 foo::collect(...)foo::get_sum() 返回不同的结果。我怀疑后者可能只是由于通过库边界实现定义的行为,假设foo::collect(...) 被内联到包含foo::get_sum() 的库中——在这种情况下main 可能看不到foo::get_sum 已经在之前的图书馆。
  • @Human-Compiler:这只是让每个变量分别存在于每个动态对象(无论 foo::collect 是否内联)的常见 Windows 方法。我倾向于将其视为“不符合标准”而不是“动态库未标准化”,因为只要链接在程序启动之前完成就无关紧要了。
猜你喜欢
  • 2016-04-27
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2010-09-16
  • 2017-12-24
  • 1970-01-01
  • 2011-12-08
  • 2015-02-19
相关资源
最近更新 更多