2010-09-09 111 views
3

這種情況只能發生沒有名稱重整(我相信),所以下面的代碼是C. 說有在交流中定義的函數A作爲將「太多」參數傳遞給外部函數是否安全?

void A(int x, int y){ 
    //Do stuff 
} 

現在也有一個單獨的文件BC:

extern "C"{ 
    void A(int x, int y, int z); 
} 

void B(){ 
    A(1, 2, 3); 
} 

A最初聲明爲只有2個參數,但在Bc中聲明時,它有一個額外的參數,並且它在B()中被第三個參數調用。 我知道有可能出現這種情況,例如與fortran子例程鏈接時,或者動態鏈接時。

我想將一個額外的參數傳遞給函數是不安全的,任何人都可以解釋當一個函數被調用並且參數傳遞給它時內存中發生了什麼?因此,通過這個既不使用也不想要的「額外」論證是多麼安全。

是否有可能額外的參數覆蓋功能內使用的內存空間?或者,函數調用A爲參數分配內存空間,然後告訴A參數內存塊的開始位置,A讀出前兩個參數並忽略最後一個,使其完全安全?

關於該功能的任何信息都會非常有啓發性,謝謝。

+0

請注意,這裏幾乎所有的答案都假定爲x86 - 在幾個平臺上,這可能永遠不會有效。它完全取決於平臺和調用約定。 – 2010-09-09 14:39:06

回答

4

聯動是實現定義的,所以沒有辦法肯定地說。也就是說,C的其他功能(特別是vardic參數)強制執行通常允許的功能。

例如,我不知道,如果你寫了會失敗任何實現的:

printf("%d", 1, 2); 

它將,然而,僅僅是打印出「1」。

這裏很多人都在調用cdecl,pascal__stdcall調用約定。然而,這些都不是標準的一部分,並且都是某些實施的特徵。這將我們帶回到第一句話。

+0

+1來補償所有其他upvoted蹩腳的答案。 – 2010-09-09 16:56:58

+0

可變參數函數的存在實際上並不會強制編譯器對非可變參數函數使用特定的調用約定。事實上,這恰恰是*爲什麼必須使用範圍內的正確原型調用可變參數函數 - 以允許編譯器使用特殊的調用約定。 – caf 2010-09-10 02:27:15

0

這樣的代碼違反了One Definition Rule(好吧,C等效於它......)無論它是否有效都完全是平臺特定的。

具體在x86上,如果函數被宣佈__cdecl,那麼它會工作,因爲調用者清理堆棧,但如果是__stdcall(因爲大多數的Win32函數),被調用方清理堆棧,並會清理在這種情況下是錯誤的(因爲它有太多的參數)。因此它將取決於所使用的外部函數的調用約定。

我不明白你爲什麼要這樣做,但是。

+0

我認爲你的意思是「如果它是'__stdcall'(就像大多數的win32函數一樣),**被調用者**清除堆棧」。 – DrAl 2010-09-09 14:40:48

+0

用'__stdcall',你是指'被調用者'還是'被調用函數'?目前它再次說'呼叫者'。 – 2010-09-09 14:47:01

+0

@Al + @Jonathan:謝謝你們。固定。必須詛咒所有的大腦屁... – 2010-09-09 15:29:58

4

這取決於所使用的調用約定。在cdecl中,調用者以從右到左的順序將參數壓入堆棧,然後被調用者通過偏移堆棧指針來訪問它們。在這種情況下調用太多的參數不會破壞任何東西。

但是,如果您有一個從左到右的調用約定,那麼事情就會中斷。

+3

實際上,這裏的關鍵不僅僅是'cdecl'從左到右,還有'cdecl'意味着負責清理堆棧中的參數的函數是相同的推動他們。另一方面,'stdcall'以相同的順序推入參數,但依靠被調用的函數來清理堆棧,這會破壞堆棧。 – torak 2010-09-09 14:37:53

+0

@torak:明白。但是如果有一個從左到右的調用約定(我想不出一個例子,但是),被調用者會看到無意義的論點。所以事情會以不同的方式打破。 – 2010-09-09 14:40:47

+0

有關x86調用約定的列表,請參閱http://en.wikipedia.org/wiki/X86_calling_conventions。 「帕斯卡」會議是從左向右推動的。 – torak 2010-09-09 15:07:04

3

隨着cdeclcalling convention,調用者負責清理堆棧,所以這將是安全的。相比之下,pascal調用約定使被調用者負責清理,所以這會很危險。

+2

+1 - 注意'__stdcall'也需要被調用者清理堆棧。還要注意,調用約定完全是平臺特定的,標準對它們完全沒有提及。 – 2010-09-09 14:35:16

+0

-1根據實現細節給出答案,並忽略語言標準所說的內容。 – 2010-09-09 16:56:24

-1

如果我說得對,這可能會導致程序執行內存中的隨機代碼。當函數被調用時,包含返回地址(程序將在函數完成時跳轉到的地方)的幾個值被推送到堆棧。之後,函數參數(x,y,z)被推送到堆棧,並且程序跳轉到函數的入口點。然後該函數將彈出堆棧中的參數(x,y),執行某些操作,然後從堆棧中彈出返回地址(在這種情況下,這是錯誤的),並跳轉回來。

這裏的堆棧細節一個不錯的描述:http://www.tenouk.com/Bufferoverflowc/Bufferoverflow2a.html

+0

這不是默認的('__cdecl')調用約定。 – 2010-09-09 14:37:34

0

至少在C和C++中它不會造成任何傷害。參數從右向左推,被調用者負責堆棧清理。

但是,編譯器不會讓你這樣做,除非你使用可變參數或轉換函數類型。例如:

#include <stdio.h> 

static void foo (int a, int b, int c, int d, int e, int f, int g) 
{ 
    printf ("A:%d B:%d C:%d D:%d E:%d F:%d G:%d \n", 
      a, b, c, d, e, f, g); 
} 

int main() 
{ 
    typedef void (*bad_foo) (int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int); 
    foo (1, 2, 3, 4, 5, 6, 7); 
    bad_foo f = (bad_foo) (&foo); 
    f (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); 
} 

如果你看一下彙編代碼,所有的參數都推到寄存器,但額外的onces纔剛剛被忽略。

1

在C這違反了約束,因此它導致未定義的行爲。

「如果表示被調用函數的表達式的類型包含原型,則參數個數應與參數個數一致。」 (C99,§6.5.2.2)

這就是說,實際上它將主要取決於潛在的調用約定。

相關問題