据我了解,如果我在程序中调用printf
,默认情况下(如果程序没有静态编译),它会调用标准C库中的printf
。但是,如果我打电话给memcpy
,我希望代码内联,因为如果memcpy
只复制几个字节,函数调用非常昂贵。如果您有时在线并且调用其他人,则在libc升级之后程序的行为取决于实现。
在这两种情况下实际发生了什么?一般情况下?
答案 0 :(得分:2)
C标准允许实施行为"好像"调用了实际的标准库函数。这确实是一种常见的优化:小memcpy
次调用可以展开/内联,还有更多。
你是对的,在某些情况下你可以升级你的libc
而不会看到任何已经优化的函数调用的变化。
答案 1 :(得分:1)
您始终可以尝试驱动编译器行为。例如,使用gcc
:
gcc -fno-inline -fno-builtin-inline -fno-inline-functions -fno-builtin...
您应该使用nm
或直接在汇编源代码中检查中断调用来检查不同的结果。
答案 2 :(得分:1)
首先,该函数永远不会真正“内联” - 适用于您编写的在同一编译单元中可见的函数。
如果您有时在线并且在调用其他人时,libc升级后程序的行为取决于实现。
事实并非如此。 编译时可能会“内联”memcpy
。编译完成后,你的libc版本没有任何区别。
在GCC中,memcpy
被视为builtin。这意味着如果GCC决定它,对memcpy
的调用将被替换为合适的实现。在x86上,这通常是rep movsb
或类似的指令 - 取决于副本的大小,以及它是否是恒定大小。
答案 3 :(得分:1)
它依赖于很多东西,这里有你如何找到答案。 GNU Binutils附带了一个实用程序objdump
,它提供了有关二进制文件中的内容的各种详细信息。
在我的系统(ARM Chromebook)上,编译test.c:
#include <stdio.h>
int main(void) {
printf("Hello, world!\n");
}
使用gcc test.c -o test
,然后运行objdump -R test
给出
test: file format elf32-littlearm
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
000105e4 R_ARM_GLOB_DAT __gmon_start__
000105d4 R_ARM_JUMP_SLOT puts
000105d8 R_ARM_JUMP_SLOT __libc_start_main
000105dc R_ARM_JUMP_SLOT __gmon_start__
000105e0 R_ARM_JUMP_SLOT abort
这些是文件中的动态重定位条目,所有将从二进制文件外部的库链接的内容。这里似乎printf
已完全优化,因为它只提供一个常量字符串,因此puts
就足够了。如果我们将其修改为
printf("Hello world #%d\n", 1);
然后我们得到预期的
000105e0 R_ARM_JUMP_SLOT printf
要明确链接memcpy
,我们必须阻止gcc
将自己的内置版本与-fno-buildin-memcpy
一起使用。