簡體   English   中英

參數傳遞給函數

[英]Argument is passed to function

請考慮以下來源:main.c

int main(){
    print_hello("str");
    return 0;
}

和你好

#include<stdio.h>
void print_hello(){
    printf("Hello world\n");
}

編譯和鏈接gcc -o hello hello.c main.c它工作正常,但我預計會發生錯誤。 因為main.c print_hellohello.c中的print_hello簽名是不同的。 為什么它工作正常?

你沒有告訴編譯器print_hello()函數在main.c文件中是什么樣的。 如果你有一個頭文件hello.h例如:

#ifndef HELLO_H_INCLUDED
#define HELLO_H_INCLUDED

extern void print_hello(void);

#endif

你在每個文件的頂部都有#include "hello.h" ,然后你會在main.c濫用print_hello()來獲得相關的編譯器警告。

您的編譯器使用調用約定 ,它允許額外的參數傳遞給被調用的函 這在C的調用約定之間是典型的,因為它支持變量參數列表(如printf,scanf等)。在這些約定中,

  • 參數以第一個參數最接近堆棧頂部(如果所有參數都放在堆棧上)或等效寄存器中的順序傳遞,其他參數按順序排列,
  • 調用者知道使用的堆棧大小並在函數調用后恢復堆棧指針。

X86調用約定中,這通常被命名為“cdecl”(可選地,帶有1或2個下划線)。

這樣的約定可以容忍額外的參數,但是,絕對不能用於被調用函數使用的未指定的參數; 后一種情況導致參數中的垃圾。

通常不應該利用這樣的運行時功能,但有一些極端情況它是有用的。

為了防止函數使用與其定義不匹配,您應該對兩者使用相同的聲明。 通常,它會在聲明或調用此函數的所有源中包含相同的頭文件。

除了其他解釋之外,假設在Linux上使用GCC 4.8或更高版本:

您應該使用gcc -Wall -std=c99進行編譯以獲取所有警告( -Wall )並告訴編譯器您要遵守的標准( -std=c99 )。 使用這些設置,您會收到警告:

  main.c: In function 'main':
  main.c:2:5: warning: implicit declaration of function 'print_hello' 
              [-Wimplicit-function-declaration]
  print_hello("str");
 ^

至於為什么沒有給出錯誤,它們只能在鏈接器的鏈接時間給出( ld ,由gcc調用)。 Unix和Linux鏈接器(來自GNU binutils )只能使用名稱:每個函數(更常見的是每個鏈接符號)都有一個名稱(並且目標文件和可執行文件都在ELF中 ,它定義了名稱的表示方式等等... 。)如果兩個名稱相同,則鏈接器通過使它們引用相同的地址來解析(參見Levine的鏈接器和加載器 )。 因此,由於main.ohello.o中的兩個名稱(或符號)都是print_hello因此它們被鏈接在一起。 使用nm查詢ELF對象或可執行文件中的名稱。 當然,在運行時發生的事情是未定義的行為 ,但x86 (和x86-64 )的ABI約定啟用了您期望的行為。 重要的是ELF不會在目標文件中保留輸入或簽名元數據。

使用C ++,它有點不同:編譯器通過名稱修改來轉換函數名稱(因此ELF *.o文件中的名稱不僅僅是C ++源名稱),因此鏈接器會看到不同的名稱(並且會失敗)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM