【发布时间】:2019-03-31 22:55:41
【问题描述】:
我正在试验 gcc 和 clang 看看它们是否可以优化
#define SCOPE static
SCOPE const struct wrap_ { const int x; } ptr = { 42 /*==0x2a*/ };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
返回一个中间常数。
事实证明他们可以:
0000000000000010 <ret_global>:
10: b8 2a 00 00 00 mov $0x2a,%eax
15: c3 retq
但令人惊讶的是,删除静态会产生相同的汇编输出。
这让我很好奇,因为如果全局不是 static 它应该是可插入的,并且用中间值替换引用应该可以防止全局变量上的插入。
确实如此:
#!/bin/sh -eu
: ${CC:=gcc}
cat > lib.c <<EOF
int ret_42(void) { return 42; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 42 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
int ret_global(void) { return w.ptr->x; }
int ret_fn_result(void) { return ret_42()+1; }
EOF
cat > lib_override.c <<EOF
int ret_42(void) { return 50; }
#define SCOPE
SCOPE const struct wrap_ { const int x; } ptr = { 60 };
SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };
EOF
cat > main.c <<EOF
#include <stdio.h>
int ret_42(void), ret_global(void), ret_fn_result(void);
struct wrap_ { const int x; };
extern struct wrap { const struct wrap_ *ptr; } const w;
int main(void)
{
printf("ret_42()=%d\n", ret_42());
printf("ret_fn_result()=%d\n", ret_fn_result());
printf("ret_global()=%d\n", ret_global());
printf("w.ptr->x=%d\n",w.ptr->x);
}
EOF
for c in *.c; do
$CC -fpic -O2 $c -c
#$CC -fpic -O2 $c -c -fno-semantic-interposition
done
$CC lib.o -o lib.so -shared
$CC lib_override.o -o lib_override.so -shared
$CC main.o $PWD/lib.so
export LD_LIBRARY_PATH=$PWD
./a.out
LD_PRELOAD=$PWD/lib_override.so ./a.out
输出
ret_42()=42
ret_fn_result()=43
ret_global()=42
w.ptr->x=42
ret_42()=50
ret_fn_result()=51
ret_global()=42
w.ptr->x=60
编译器可以用中间体替换外部全局变量的 refs 吗?那些不应该也是可插入的吗?
编辑:
Gcc不优化外部函数调用(除非使用 -fno-semantic-interposition 编译)
例如在int ret_fn_result(void) { return ret_42()+1; } 中对ret_42() 的调用,尽管与对extern global const 变量的引用一样,改变符号定义的唯一方法是通过插入。
0000000000000020 <ret_fn_result>:
20: 48 83 ec 08 sub $0x8,%rsp
24: e8 00 00 00 00 callq 29 <ret_fn_result+0x9>
29: 48 83 c4 08 add $0x8,%rsp
2d: 83 c0 01 add $0x1,%eax
我一直认为这是为了允许插入符号。顺便说一句,clang 确实优化了它们。
我想知道它在哪里(如果有的话)说 ret_global() 中对 extern const w 的引用可以优化为中间体,而 ret_fn_result 中对 ret_42() 的调用不能。
无论如何,除非您建立翻译单元边界,否则符号迭代似乎在不同编译器之间非常不一致且不可靠。 :/
(如果所有全局变量都可以始终插入,除非-fno-semantic-interposition 开启,但我们只能希望。)
【问题讨论】:
-
由于
ret_global和ret_42地址直到加载时才知道,我希望ret_global和ret_42成为过程链接表 (PLT) 的一部分。这将在加载期间修复。 -
@MichaelPetch 我是个偶像。我的 liboverride 应该是
#define SCOPE SCOPE const struct wrap_ { const int x; } ptr = { 60 }; SCOPE struct wrap { const struct wrap_ *ptr; } const w = { &ptr };来覆盖变量,而不是函数。但是在所有情况下我都得到 42。我想知道变量是否也不应该是可插入的。 -
@MichaelPetch 我会修改这个问题,因为还没有人在答案中解决它。
-
@MichaelPetch 所以现在的问题是:extern globals 不应该像 extern fucntions 一样总是可以插入的吗?
-
@PSkocik,让他们不要
const:)0000000000001100 <ret_global>: 1100: 48 8b 05 e1 2e 00 00 mov 0x2ee1(%rip),%rax # 3fe8 <w@@Base-0x40> 1107: 48 8b 00 mov (%rax),%rax 110a: 8b 00 mov (%rax),%eax 110c: c3 retq
标签: c optimization linker compiler-construction elf