如果C++的inline函数中有宏,行为是怎样的?
天蓬老师
天蓬老师 2017-04-17 14:39:14
0
4
635

这里是几份C++代码:

a.h

#include <iostream>
#ifndef STR
#define STR "default"
#endif

struct A {
    static void print() { std::cout << STR; }
};

imp1.cc

#define STR "imp1"
#include "a.h"
void imp1() { A::print(); }

imp2.cc

#define STR "imp2"
#include "a.h"
void imp2() { A::print(); }

main.cc

void imp1();
void imp2();
int main() { imp1(); imp2(); }

我们知道,在类定义中定义的函数默认是inline函数,inline函数将建议编译器在行内展开代码。那么main打印的是imp1, imp2还是str?

我找不到相关的资料。相同的代码,编译命令为g++ main.cc imp1.cc imp2.cc时,在GCC 6.1.1上面是打印imp1imp2,在MinGW GCC 4.8.1上面是打印imp1imp1。在编译命令为g++ imp2.cc imp1.cc main.cc,在GCC 6.1.1上面没有变化,在MinGW GCC 4.8.1上面是打印imp2imp2

请问C++ standard有相关规定(也就是说是MinGW的bug),还是未定义行为呢?

天蓬老师
天蓬老师

欢迎选择我的课程,让我们一起见证您的进步~~

全部回覆(4)
洪涛

試了一下,gcc 如果 -O2 或 -O1 優化,出來的結果 imp1imp2 這樣的,如果是 -O0 優化,出來的 imp1imp1 或 imp2imp2

原因應該是在沒有 -O 最佳化的情況下 inline 會被 gcc 忽略,以方便除錯。

標準裡 inline 應該是一個 suggestion,而不是一個強制的要求。如果沒有 inline,.o 檔案的順序就成了決定性因素。

$ gcc -v
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/6.1.1/lto-wrapper
目标:x86_64-pc-linux-gnu
配置为:/build/gcc-multilib/src/gcc/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --enable-libmpx --with-system-zlib --with-isl --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --enable-gnu-indirect-function --enable-multilib --disable-werror --enable-checking=release
线程模型:posix
gcc 版本 6.1.1 20160802 (GCC) 
大家讲道理

應該是GCC沒有進行inline(即使你寫了inline,編譯器也不保證進行inline),這樣的話,到了鏈接階段,對於A::print的引用,用哪個.o文件中的A:: print都有可能(都是弱符號),但一定只會使用一個。

大家讲道理

樓主需要搞清楚編譯的過程,
1.預處理(Preprocessing)
2.編譯(Compilation)
3.彙編(Assembly)
4.連結(Linking)

define在預處理階段
inline在編譯階段

這樣應該明白了吧

黄舟

環境:

Apple LLVM version 8.0.0 (clang-800.0.38)
Target: x86_64-apple-darwin16.0.0
Thread model: posix

clang -O0 -O1 的結果是 imp1imp1
clang -O2 -O3 的結果是 imp1imp2

我將程式碼修改了一下,強制使用內聯:

struct A {
    __attribute__((always_inline)) static void print() { std::cout << STR; }
};

在任何最佳化開關下都是 imp1imp2

而當程式碼強制禁止內聯的時候:

struct A {
    __attribute__((noinline)) static void print() { std::cout << STR; }
};

結果與文件順序相關:
如果imp1.cpp 排在前面,在任何最佳化開關下都是imp1imp1
如果imp2.cpp 排在前面,在任何最佳化開關下都是imp2imp2

解釋如下:
強制內聯時,內聯函數的宏會在兩個檔案中被獨立的替換;
禁止內聯時,內聯函數的宏會在第一個編譯單元被替換(因為如果在多個編譯單元被替換就等於內聯了)。
未強制的時候(無 __attribute__)在 -O0 -O1下,編譯器會關閉內聯,而 -O2 -O3 下才開啟。

除了成員函數之外,如果是普通內聯函數(non-member,non-static-member)定義在頭文件裡,會造成編譯錯誤,因為重定義;而定義在cpp 裡則根本不會被其他文件的巨集所影響。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板