程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C函數調用原理

C函數調用原理

編輯:關於C語言

C函數調用原理


 

函數調用處理過程

 

The C calling convention in 16-bit programs is as follows. In the following description, the words caller and callee are used to denote the function doing the calling and the function which gets called.

  • The caller pushes the function's parameters on the stack, one after another, in reverse order (right to left, so that the first argument specified to the function is pushed last).The caller then executes a CALL instruction to pass control to the callee. This CALL is either near or far depending on the memory model.The callee receives control, and typically (although this is not actually necessary, in functions which do not need to access their parameters) starts by saving the value of SP in BP so as to be able to use BP as a base pointer to find its parameters on the stack. However, the caller was probably doing this too, so part of the calling convention states that BP must be preserved by any C function. Hence the callee, if it is going to set up BP as a frame pointer, must push the previous value first.The callee may then access its parameters relative to BP. The word at [BP] holds the previous value of BP as it was pushed; the next word, at [BP+2], holds the offset part of the return address, pushed implicitly byCALL. In a small-model (near) function, the parameters start after that, at [BP+4]; in a large-model (far) function, the segment part of the return address lives at [BP+4], and the parameters begin at [BP+6]. The leftmost parameter of the function, since it was pushed last, is accessible at this offset from BP; the others follow, at successively greater offsets. Thus, in a function such as printf which takes a variable number of parameters, the pushing of the parameters in reverse order means that the function knows where to find its first parameter, which tells it the number and type of the remaining ones.The callee may also wish to decrease SP further, so as to allocate space on the stack for local variables, which will then be accessible at negative offsets from BP.The callee, if it wishes to return a value to the caller, should leave the value in AL, AX or DX:AX depending on the size of the value. Floating-point results are sometimes (depending on the compiler) returned in ST0.Once the callee has finished processing, it restores SP from BP if it had allocated local stack space, then pops the previous value of BP, and returns via RETN or RETF depending on memory model.When the caller regains control from the callee, the function parameters are still on the stack, so it typically adds an immediate constant to SP to remove them (instead of executing a number of slow POPinstructions). Thus, if a function is accidentally called with the wrong number of parameters due to a prototype mismatch, the stack will still be returned to a sensible state since the caller, which knows how many parameters it pushed, does the removing. 引用自NASM手冊

    調用過程示例及測試用例

    /*
    	 
    以三個參數為例子,AT&T32匯編,棧向低地址增長
    fun(p0, p1, p2);
    1.參數入棧,right->left
    	gcc的做法:
           其中p0 p1 p2姑且認為是立即數
    	movl $p2 8(%esp)
    	movl $p1 4(%esp)
    	movl $p0 (%esp)
    
    
     	 |_______|
    ebp->|_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|<-esp
     	 |_______|
     	 |_______|
     	 |_______|
    
    
    2.call fun
    
    
     	 |_______|
    ebp->|_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|
     	 |__ret__|<-esp
     	 |_______|
     	 |_______|
    
    
    3.跳轉到fun代碼
    	push %ebp
    	movl %esp, %ebp
     
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|
     	 |__ret__|
    ebp->|__ebp__|<-esp
     	 |_______|
     	 |_______|
     
    其中存儲的ebp是第2步中的ebp,即之前的ebp,不是圖左面的ebp
    如果棧向低地址增長
    那麼
    old ebp = [ebp]
    ret = [ebp + 4]
    p0 = [ebp + 8]
    p1 = [ebp + 12]
    p2 = [ebp + 16]
     
    4.
    decrease ESP further, so as to allocate space on the stack for local variables,
    which will then be accessible at negative offsets from EBP.
     
    	subl $24, %esp
    	根據具體需要分配棧空間,這裡示例24/4=6個4B局部變量
    
    
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|
     	 |__ret__|
    ebp->|__ebp__|
     	 |_______|
     	 |_______|
     	 |_______|<= esp + 12 = ebp - 12
     	 |_______|
     	 |_______|
     	 |_______|<-esp
     	 |_______|
     	 |_______|
     
     
    5.返回
    	5.1 調整棧指針
    	方式1:addl	$24, %esp
    	方式2:movl %ebp, %esp
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|
     	 |__ret__|
    ebp->|__ebp__|<-esp
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
    
    
    	 5.2彈出舊frame指針=銷毀當前棧幀
    	popl	%ebp
    
    
     	 |_______|
    ebp->|_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|
     	 |__ret__|<-esp
    	 |__ebp__|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |_______|
    
    
    	5.3 返回調主
    	ret
     
     	 |_______|
    ebp->|_______|
     	 |_______|
     	 |_______|
     	 |_______|
     	 |__p2___|
     	 |__p1___|
     	 |__p0___|<-esp
     	 |__ret__|
    	 |__ebp__|
     	 |_______|
     	 |_______|
     	 |_______|
    
    
    	上面說要調整esp,把參數移出掉。gcc沒有做這一步。esp就保留在這裡
    當下面再次調用函數時,舊值就被覆蓋掉。參看第一步!
    
    
    5.1和5.2步,可以使用leave指令代替
    
    
     */
    
    
    //驗證
    #include 
    #include  
    void fun(int a, int b)
    {
    	int local;
    	unsigned int i, j;
    	long ret;
    
    
    	local = 12;
    	a = 10;
    //	printf("%d\n", a);
    #if 1
    	asm volatile (
    			"movl %%ebp, %%eax\n\t"
    			"movl %%esp, %%ebx\n\t"
    			:"=a"(i), "=b"(j)
    			:);
    
    
    	printf("ebp = %p; esp = %p\n", i, j);
    	printf("local auto var : %d\n", i - j); //局部變量占用空間
    
    
    	asm volatile (
    			"movl 12(%%ebp), %%eax\n\t"//參數b
    			"movl 8(%%ebp), %%ebx\n\t" //參數a
    			"movl 4(%%ebp), %%ecx\n\t" //ret
    			:"=a"(i), "=b"(j), "=c"(ret)
    			:);
    	printf("p2 = %x; p1 = %x; ret = %p\n", i, j, ret);
    #endif
    }
    
    
    int main(int argc, char *argv[])
    {
    	fun(1, 2);
    	exit(0);
    }

    總結

    1-如果自行編寫匯編,那麼就要自行處理這些工作,以及對某些寄存器的壓棧出棧的保護措施

    2-第5.2步,當ebp彈出後,一個棧幀就不存在了,即函數環境已經撤銷,因此如果fun內return一個局部變量的引用,也就無意義了。這是最常見的問題!!!

    3-局部變量空間的分配,換言之,第4步,對esp的調整,其大小要根據編譯時符號表而定!

     

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved