Sergey Kolesnik
Sergey Kolesnik

Reputation: 3678

why static inline function with static variable behave so differently?

I am struggling to understand why there is such a difference in behavior across compilers and platforms.

Here is an extended example of http://kyungminlee.org/doc/minutiae/local_static_variable_shared_library.html.

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)

collect.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();
};

collect.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 output:

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

Clang

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 and Clang give the same output

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

Results look somewhat consistent only on Ubuntu. On Windows only MSVC behaves differently. More over MSVC differs not only from MinGW and Clang, but also from GCC and Clang on Ubuntu.

I suppose for MSVC on Windows the result we see is because of the linker removes duplicate symbols of inlined functions and member functions:

  1. Does the standard require linker to remove duplicate symbols of inlined functions?
    For inlined functions local static members (as I remember) are guaranteed to be at the same address:

Function-local static objects in all definitions of the same inline function (which may be implicitly inline) all refer to the same object defined in one translation unit.
2. What about static member inline functions with function-local static objects? Do They refer to the same object across translation units?

  1. How ABI friendly is it to rely on a static inline functions and static inline member functions for a library or a core application that loads plugins? For example, if a type id is stored as a function-local static object within inline static function/inline static member function?
    • I am aware, for example, that any changes to inline function body will break the ABI. But apart from that?
class __declspec(dllexport) counter
{
   static int get() // implicitly inline
   {
      static int current = 0;
      return current++;
   }
};

Upvotes: 1

Views: 598

Answers (1)

Davis Herring
Davis Herring

Reputation: 40063

With #define STATIC static, the ::collect that main calls is simply a different function from the one that ::get_sum calls, so I don’t know what other behavior you expect. This doesn’t apply to member functions, static or otherwise; static means something completely different there, and the multiple definitions of foo all define the same type with the same member functions.

Upvotes: 1

Related Questions