1. 程式人生 > >C/C++函式引數的入棧順序,計算順序和可變引數的實現

C/C++函式引數的入棧順序,計算順序和可變引數的實現

函式引數入棧順序

#include
void foo(int x, int y, int z)
{
        printf("x = %d at [%X]\n", x, &x);
        printf("y = %d at [%X]\n", y, &y);
        printf("z = %d at [%X]\n", z, &z);
}
int main(int argc, char *argv[])
{
        foo(100, 200, 300);
        return 0;
}

執行結果是:
x = 100 at […60]

y = 200 at […64]

z = 300 at […68]

這是由於,C程式棧的記憶體生長方式是往低地址記憶體生長,這也說明為什麼區域性變數無法申請太大記憶體,因為棧內容有限。此外,這個例子說明,函式引數的入棧的順序是從右往左的!。引數入棧順序具體的還與編譯器相關,涉及到C語言中呼叫約定所採用的方式:

C呼叫約定在返回前,要作一次堆疊平衡,也就是引數入棧了多少位元組,就要彈出來多少位元組.這樣很安全.

有一點需要注意:stdcall呼叫約定如果採用了不定引數,即VARARG的話,則和C呼叫約定一樣,要由呼叫者來作堆疊平衡.

(1)_stdcall是 Pascal方式清理C方式壓棧,通常用於Win32 Api中,函式採用從右到左的壓棧方式,自己在退出時清空堆疊。VC將函式編譯後會在函式名前面加上下劃線字首,在函式名後加上”@”和引數的位元組數。 int f(void *p) –>>

[email protected](在外部組合語言裡可以用這個名字引用這個函式)在WIN32 API中,只有少數幾個函式,如wspintf函式是採用C呼叫約定,其他都是stdcall

(2)C呼叫約定(即用 __cdecl關鍵字說明)(The C default calling convention)按從右至左的順序壓引數入棧,由呼叫者把引數彈出棧。對於傳送引數的記憶體棧是由呼叫者來維護的(正因為如此,實現可變引數 vararg的函式(如printf)只能使用該呼叫約定)。另外,在函式名修飾約定方面也有所不同。 _cdecl是C和C++程式的預設呼叫方式。每一個呼叫它的函式都包含清空堆疊的程式碼,所以產生的可執行檔案大小會比呼叫_stdcall函式的大。函 數採用從右到左的壓棧方式。VC將函式編譯後會在函式名前面加上下劃線字首。

(3)__fastcall呼叫的主 要特點就是快,因為它是通過暫存器來傳送引數的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的引數,剩下的引數仍舊自右向左壓棧傳 送,被呼叫的函式在返回前清理傳送引數的記憶體棧),在函式名修飾約定方面,它和前兩者均不同。__fastcall方式的函式採用暫存器傳遞引數,VC將 函式編譯後會在函式名前面加上”@”字首,在函式名後加上”@”和引數的位元組數。

(4)thiscall僅僅應用於”C++”成員函式。this指標存放於CX/ECX暫存器中,引數從右到左壓。thiscall不是關鍵詞,因此不能被程式設計師指定。

(5)naked call。 當採用1-4的呼叫約定時,如果必要的話,進入函式時編譯器會產生程式碼來儲存ESI,EDI,EBX,EBP暫存器,退出函式時則產生程式碼恢復這些暫存器的內容。

綜上,其實只有PASCAL呼叫約定的從左到右入棧的.而且PASCAL不能使用不定引數個數,其引數個數是一定的。

可變引數的實現

支援可變引數的__cdecl呼叫其實可以理解的。C方式入棧順序從右往左,那麼在棧底的元素就是可變引數的最右邊一個,我們只需要知道所有明確引數裡的最左邊一個引數在棧中的位置,剩下到棧底的都是可變引數了,反之如果從左往右入棧,則無法知道最右邊的可變引數在棧中的位置。在具體實現中,也可觀察到其中的原理,包括,需要呼叫者手動清棧。

float averge(int n_values, ...)
{
    va_list var_arg;
    // 準備訪問可變引數
    va_start(var_arg, n_values);// 第一個引數是va_list變數的名字,第2個引數是省略號前最後一個有名字的引數
    // 取值
    for(::)
        sum += va_arg(var_arg, int);// 第二個引數是引數的型別
    // 完成處理可變引數,手動清棧
    va_end(var_arg);
}

結論很簡單:如果支援可變引數的函式,那麼引數進棧的順序幾乎必然是自右向左 的。並且,引數出棧也不能由函式自己完成,而應該由呼叫者完成。

函式引數計算順序

主要想說明的是,函式的引數壓棧順序和引數計算順序不是一個概念。一個函式帶有多個引數的時,C++語言沒有規定函式呼叫時實參的求值順序。這個是編譯器自己規定的。

比方說int z = add(++x,x+y);不同編譯器可能產生不同結果。

相關推薦

C/C++函式引數順序計算順序可變引數實現

函式引數入棧順序 #include void foo(int x, int y, int z) { printf("x = %d at [%X]\n", x, &x); printf("y = %d at [%X]\

C語言函式引數的彙編理解

先來看這樣一段程式: #include <string.h> #include <stdlib.h> #include <stdio.h> void print1(int a,int b,int c) { printf("%p

順序C語言實現——初始化函式函式函式

將順序棧的結構定義為: #define  M  100   //棧的空間 typedef struct   {   int data[M]; int top;   } SqStack; 試寫出SqStack的初始化函式、入棧函式和出棧函式 。並在main()函式中測試上述

使用ctypes呼叫系統C API函式需要注意的問題函式引數中有指標或結構體的情況下最好不要修改argtypes

有人向我反應,在程式碼裡同時用我的python模組uiautomation和其它另一個模組後,指令碼執行時會報錯,但單獨使用任意一個模組時都是正常的,沒有錯誤。 我用一個例子來演示下這個問題是如何出現的。 假設我需要寫一個module,這個module需要提供獲取當前滑鼠游標下視窗控制代碼的功能,這需要呼

C語言 函式返回一位陣列二維陣列

方法一: 萬能的結構體:構造陣列的結構體,將函式型別定義為此型別 但是考試的時候應該不太方便寫結構體,寫不下也會很麻煩,故介紹方法二 方法二: 指標傳遞: 1、返回一維陣列 例子:將陣列每一位加一: #include<stdio.h> #define N 10 int

c++11函式模板的預設模板引數 可變引數模板函式

轉自:https://www.cnblogs.com/lsgxeva/p/7787500.html #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <v

C語言printf函式輸出表達式中的計算順序

看下面一個例子: #include <stdio.h> main() {     int i=8;     printf%d, %d, %d, %d\n",i,--i,i,i--);

關於函式引數的思考

程式碼開發執行環境: VS2017+Win32+Debug 1.呼叫約定簡介 首先,要實現函式呼叫,除了要知道函式的入口地址外,還要向函式傳遞合適的引數。向被調函式傳遞引數,可以有不同的方式實現。這些方式被稱為“呼叫規範”或“呼叫約定”。C/C++中

C++的拷貝建構函式、operator=運算子過載深拷貝淺拷貝、explicit關鍵字

1、在C++編碼過程中,類的建立十分頻繁。 簡單的功能,當然不用考慮太多,但是從進一步深刻理解C++的內涵,類的結構和用法,編寫更好的程式碼的角度去考慮,我們就需要用到標題所提到的這些內容。 最近,在看單例模式,覺得十分有趣,然而如果想要掌握單例模式,就必須掌握這些內容。下

c/c++常變數存放。堆區靜態區

學習語言,首先等知曉所敲下的程式碼做了什麼,而程式碼中最重要的就是變數與常量,這些構成函式,產生功效。 首先得清楚以下幾個知識點(或者說專有名詞) 1.生命週期:變數的作用範圍,類似生物的存活週期,對變數的使用得在其生命週期內。很好理解。 2. 記憶體:記憶體儲器的儲存

C語言函式呼叫及幀結構

一、地址空間與實體記憶體 (1)地址空間與實體記憶體是兩個完全不同的概念,真正的程式碼及資料都存在實體記憶體中。 物理儲存器是指實際存在的具體儲存器晶片,CPU在操縱物理儲存器的時候都把他們當做記憶體來對待,把他們看成由若干個儲存單元組成的邏輯儲存器,這個邏

C語言中的函式列印乘法口訣表行數列數可以任意輸入

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> void print_table(int line) { int i = 0;

printf 引數順序

先看一段程式碼 a=1; printf("%d %d\n",a,a++); a=1; printf("%d %d\n",a++,a); a=1; printf("%d %d %d

C++模板中的省略號可變引數模板

#include <iostream> using namespace std; void print() { cout << "hello world" << endl; } template<class T> voi

c語言判斷是否是utf8字串計算字元個數

#include <stdio.h> #include <string.h> #include <stdlib.h> /******************************************************************

C++11:深入理解右值引用move語義完美轉發

深入右值引用,move語義和完美轉發 轉載請註明:http://blog.csdn.net/booirror/article/details/45057689 乍看起來,move語義使得你可以用廉價的move賦值替代昂貴的copy賦值,完美轉發使得你可以將傳來的任意

【面試題】實現一個要求Push(Pop(出Min(返回最小值的操作)的時間複雜度為O(1)

問題描述:實現一個棧,要求Push(入棧),Pop(出棧),Min(返回最小值的操作)的時間複雜度為O(1)  分析問題:要記錄從當前棧頂到棧底元素的最小值,很容易想到用一個變數,每push一個元素更新一次變數的值。那麼問題來了,當執行pop操作時,上一次的最小值就找不到

已知一個序列判斷給定的一個序列是否為其出序列

思路:(1)棧為後入先出序列(2)將入棧序列的元素一個一個入棧然後和出序列中每個元素比較。取序列第一個元素時,把第一個序列的各個元素依次入棧,然後入棧一個後和序列元素比較,如果相同那麼將這個元素出棧,並

PL/SQL函式的傳入引數名稱要規範不能函式內使用到的表的欄位名稱一樣否則會出錯(帶例子)

CREATE OR REPLACE FUNCTION pf_limit_ldlc_test(companyid IN NUMBER ,doctype IN NUMBER) RETURN NUMBER IS cnt NUMBER; BEGIN cnt := 0; IF do

【100題】給定序列判斷一個序列是否可能為輸出序列

#include <stack> #include <iostream> using namespace std; /* 假設序列中無重複數字 輸入序列為: 1,2,3,4,5 測試序列:4,5,3,2,1 測試序列:4,3,5,1,2 題目拓展