我的理解是,如果我在程序中調用printf
,默認情況下(如果程序沒有靜態編譯),它會在標準C庫中調用printf
。但是,如果我打電話給memcpy
,我希望代碼將被內聯,因爲如果memcpy
僅複製幾個字節,則函數調用非常昂貴。如果有時內聯並調用其他方法,則libc升級後的程序行爲取決於實現。printf和memcpy鏈接到標準C庫
實際上在這兩種情況下都會發生什麼情況?
我的理解是,如果我在程序中調用printf
,默認情況下(如果程序沒有靜態編譯),它會在標準C庫中調用printf
。但是,如果我打電話給memcpy
,我希望代碼將被內聯,因爲如果memcpy
僅複製幾個字節,則函數調用非常昂貴。如果有時內聯並調用其他方法,則libc升級後的程序行爲取決於實現。printf和memcpy鏈接到標準C庫
實際上在這兩種情況下都會發生什麼情況?
這將取決於很多事情,這裏是你如何找出答案。 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
。
C標準允許一個實現來表現「好像」實際標準庫函數被調用。這確實是一個常見的優化:小的memcpy
調用可以展開/內聯,等等。
你是對的,在某些情況下,你可以升級你的libc
,而不會看到任何優化過的函數調用的變化。
您可以隨時嘗試驅動編譯器行爲。例如,對於gcc
:
gcc -fno-inline -fno-builtin-inline -fno-inline-functions -fno-builtin...
您應該檢查與nm
或直接在彙編源代碼的中斷調用不同的結果。
首先,函數永遠不會真正「內聯」 - 適用於您編寫的功能在同一個編譯單元中可見。
如果您有時正在內聯並調用其他人,則在升級libc後程序的行爲取決於實現。
事實並非如此。 memcpy
可能在編譯時間處「內聯」。編譯完成後,您的libc版本沒有任何區別。
在GCC中,memcpy
被識別爲builtin。這意味着如果GCC決定它,對memcpy
的呼叫將被替換爲合適的實現。在x86上,這通常是rep movsb
或類似的指令 - 取決於副本的大小,以及是否大小不變。