Skip to content

Documentation says [[msvc::forceinline]] is same as __forceinline, but they are not - #5967

Open
Francesco Pretto (ceztko) wants to merge 1 commit into
MicrosoftDocs:mainfrom
ceztko:main
Open

Francesco Pretto (ceztko) wants to merge 1 commit into
MicrosoftDocs:mainfrom
ceztko:main

Conversation

@ceztko

@ceztko Francesco Pretto (ceztko) commented Sep 12, 2026

Copy link
Copy Markdown

The documentation treated [[msvc::forceinline]] as a synonym of __forceinline, but they don't have the same exact behavior. In a empirical test, a Link Time Optimization enabled build of a static library had a method marked as __forceinline elided from the final objects, causing a linking error when trying to consume the static library from a non-LTO enabled exe or shared library. The method definition is not present/visible in the header. Instead, [[msvc::forceinline]] was able to both intra-optimize the static library and create a symbol for the annotated function, allowing link from a consumer which has not visibility of the definition.

For more context, the behavior of [[msvc::forceinline]] aligns with the behavior of the gcc specific always_inline[1] attribute, and clang also does the same. It's very much desidered, as generating a symbol for external consumers is more flexible than forcely eliding it.

Below the results of a probe using VS 2026 and VS 2022. The source (vibe coded with Claude Opus 5) is attached.
inline-probe.zip (updated for non MSVC)

== toolchains ==
-- 18 (18.10.12201.205): Visual Studio 18 2026
   C:\Program Files\Microsoft Visual Studio\18\Community
-- 2022 (17.14.37531.7): Visual Studio 17 2022
   C:\Program Files\Microsoft Visual Studio\2022\Community
-- cmake: cmake version 4.3.2

== results ==

VS      Annotation               LTO    Library    Exe               Shlib             Symbols     Note
----    ---------------------    ---    -------    --------------    --------------    --------    ----
18      none                     OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
18      none                     ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)
18      __forceinline            OFF    OK         LINK-FAIL         LINK-FAIL         absent
18      __forceinline            ON     OK         LINK-FAIL         LINK-FAIL         n/a (IL)
18      [[msvc::forceinline]]    OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
18      [[msvc::forceinline]]    ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)
2022    none                     OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
2022    none                     ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)
2022    __forceinline            OFF    OK         LINK-FAIL         LINK-FAIL         absent
2022    __forceinline            ON     OK         LINK-FAIL         LINK-FAIL         n/a (IL)
2022    [[msvc::forceinline]]    OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
2022    [[msvc::forceinline]]    ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)

== verdict ==
18 lto=OFF: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
18 lto=ON: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
2022 lto=OFF: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
2022 lto=ON: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it

[1] https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-always_005finline

…ut preserves the symbol in the TU

The documentation treated [[msvc::forceinline]] as a synonym of __forceinline, but
they don't have the same exact behavior. In a empirical test, a Link Time Optimization
enabled build of a static library had a method marked as __forceinline elided
from the final objects, causing a linking error when trying to consume the
static library from a non-LTO enabled exe or shared library. The method definition
is *not* present/visible in the header. Instead, [[msvc::forceinline]] was able to
both intra-optimize the static library and create a symbol for the annotated function,
allowing link from a consumer which has not visibility of the definition.

For more context, the behavior of [[msvc::forceinline]] aligns with the behavior
of the gcc specific always_inline[1] attribute, and clang also observes
the same behavior.

Below the results of a probe using VS 2026 and VS 2022.
-----------------------------------------------
== toolchains ==
-- 18 (18.10.12201.205): Visual Studio 18 2026
   C:\Program Files\Microsoft Visual Studio\18\Community
-- 2022 (17.14.37531.7): Visual Studio 17 2022
   C:\Program Files\Microsoft Visual Studio\2022\Community
-- cmake: cmake version 4.3.2

== results ==

VS      Annotation               LTO    Library    Exe               Shlib             Symbols     Note
----    ---------------------    ---    -------    --------------    --------------    --------    ----
18      none                     OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
18      none                     ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)
18      __forceinline            OFF    OK         LINK-FAIL         LINK-FAIL         absent
18      __forceinline            ON     OK         LINK-FAIL         LINK-FAIL         n/a (IL)
18      [[msvc::forceinline]]    OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
18      [[msvc::forceinline]]    ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)
2022    none                     OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
2022    none                     ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)
2022    __forceinline            OFF    OK         LINK-FAIL         LINK-FAIL         absent
2022    __forceinline            ON     OK         LINK-FAIL         LINK-FAIL         n/a (IL)
2022    [[msvc::forceinline]]    OFF    OK         LINK-OK RUN-OK    LINK-OK RUN-OK    present
2022    [[msvc::forceinline]]    ON     OK         LINK-OK RUN-OK    LINK-OK RUN-OK    n/a (IL)

== verdict ==
18 lto=OFF: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
18 lto=ON: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
2022 lto=OFF: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
2022 lto=ON: PARITY, [[msvc::forceinline]] keeps the symbol where __forceinline drops it
-----------------------------------------------

[1] https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-always_005finline
@ceztko

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@prmerger-automator

Copy link
Copy Markdown
Contributor

Francesco Pretto (@ceztko) : Thanks for your contribution! The author(s) and reviewer(s) have been notified to review your proposed change.

@prmerger-automator

Copy link
Copy Markdown
Contributor

Francesco Pretto (@ceztko) : Thanks for your contribution! The author(s) and reviewer(s) have been notified to review your proposed change.

@learn-build-service-prod

Copy link
Copy Markdown
Contributor

Learn Build status updates of commit 3d44d59:

✅ Validation status: passed

File Status Preview URL Details
docs/cpp/attributes.md ✅Succeeded
docs/cpp/inline-functions-cpp.md ✅Succeeded

For more details, please refer to the build report.

@ceztko

Francesco Pretto (ceztko) commented Sep 12, 2026

Copy link
Copy Markdown
Author

For more context outside the scope of MSVC, the probe has been tested in gcc/clang, using the gcc specific attribute always_inline which shows the same behavior as [[msvc::forceinline]].

Linux gcc/clang:

= toolchains ==
-- clang++ (clang++): Ubuntu clang version 18.1.3 (1ubuntu1)
   remedies held in reserve: ar=llvm-ar-18 ranlib=llvm-ranlib-18 linker=-Wl,--plugin=/usr/lib/llvm-18/lib/LLVMgold.so fat-lto=yes
   reporting only: nm=llvm-nm-18
-- g++ (g++): g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
   remedies held in reserve: ar=gcc-ar ranlib=gcc-ranlib linker=none fat-lto=yes
   reporting only: nm=gcc-nm
-- g++-13 (g++-13): g++-13 (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
   remedies held in reserve: ar=gcc-ar ranlib=gcc-ranlib linker=none fat-lto=yes
   reporting only: nm=gcc-nm
-- uname: Linux laptopbbkl2 6.8.0-139-generic #139-Ubuntu SMP PREEMPT_DYNAMIC Sat Aug  1 03:52:05 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux

== results ==
COMPILER   ATTR  LTO  EXE consumer     SHLIB consumer   Read()     ReadChar() ARCHIVE  REMEDY USED
clang++    ON    OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
clang++    ON    ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   -Wl,--plugin=/usr/lib/llvm-18/lib/LLVMgold.so
clang++    OFF   OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
clang++    OFF   ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   -Wl,--plugin=/usr/lib/llvm-18/lib/LLVMgold.so
g++        ON    OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++        ON    ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++        OFF   OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++        OFF   ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++-13     ON    OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++-13     ON    ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++-13     OFF   OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed
g++-13     OFF   ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none needed

== failures ==
none

== verdict ==
clang++ lto=OFF EXE: attribute is safe (annotated links and runs)
clang++ lto=OFF SHLIB: attribute is safe (annotated links and runs)
clang++ lto=ON EXE: attribute is safe (annotated links and runs)
clang++ lto=ON SHLIB: attribute is safe (annotated links and runs)
g++ lto=OFF EXE: attribute is safe (annotated links and runs)
g++ lto=OFF SHLIB: attribute is safe (annotated links and runs)
g++ lto=ON EXE: attribute is safe (annotated links and runs)
g++ lto=ON SHLIB: attribute is safe (annotated links and runs)
g++-13 lto=OFF EXE: attribute is safe (annotated links and runs)
g++-13 lto=OFF SHLIB: attribute is safe (annotated links and runs)
g++-13 lto=ON EXE: attribute is safe (annotated links and runs)
g++-13 lto=ON SHLIB: attribute is safe (annotated links and runs)

always_inline does NOT suppress the symbol on this toolchain.

MacOs Apple clang:

-- clang++ (clang++): Apple clang version 17.0.0 (clang-1700.0.13.5)
   ar=ar ranlib=ranlib nm=nm
   LTO archive remedy: linker=none fat-lto=no
-- uname: Darwin mac-mini-euronovate.local 24.6.0 Darwin Kernel Version 24.6.0: Tue Jul 21 20:50:07 PDT 2026; root:xnu-11417.140.69.711.44~1/RELEASE_X86_64 x86_64

== results ==
COMPILER   ATTR  LTO  EXE consumer     SHLIB consumer   Read()     ReadChar() ARCHIVE  LINK FIX
clang++    ON    OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none
clang++    ON    ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none
clang++    OFF   OFF  LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none
clang++    OFF   ON   LINK-OK RUN-OK   LINK-OK RUN-OK   DEFINED    DEFINED    native   none

== failures ==
none

== verdict ==
clang++ lto=OFF EXE: attribute is safe (annotated links and runs)
clang++ lto=OFF SHLIB: attribute is safe (annotated links and runs)
clang++ lto=ON EXE: attribute is safe (annotated links and runs)
clang++ lto=ON SHLIB: attribute is safe (annotated links and runs)

always_inline does NOT suppress the symbol on this toolchain.

@v-regandowner

Copy link
Copy Markdown
Contributor

Tyler Whitney (@TylerMSFT)

Can you review the proposed changes?

IMPORTANT: When the changes are ready for publication, adding a #sign-off comment is the best way to signal that the PR is ready for the review team to merge.

#label:"aq-pr-triaged"
@MicrosoftDocs/public-repo-pr-review-team

@prmerger-automator prmerger-automator Bot added the aq-pr-triaged Tracking label for the PR review team label Sep 14, 2026
@TylerMSFT

Copy link
Copy Markdown
Collaborator

Thank you for your excellent writeup. Investigating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants