c语言 swap交换函数_重审C中老生常谈的swap函数交换数值
概覽
本文內容是關于C語言參數傳值,以及x86底層實現的計算機科學。
包含了原理速覽以及代碼示例。
引言
如果你學習過C,可能會對經典的swap函數問題記憶深刻。簡單的參數傳值并不能在函數外部完成兩個數的交換,而要用指針傳地址。
對此的解釋一般為:C語言是以傳值的方式將參數傳遞給函數。因此傳遞進去的是參數的副本,縱使萬千改動也無法觸及本源絲毫。故有使用指針一說,以切實地修改兩個參數地址處的值。
但對于單純的傳值與傳指針(亦地址,引用)的區別是什么,能夠道出原委的人可能并不多。因此筆者想通過本文進入更底層的匯編領域,向大家更加清晰地闡述在底層究竟發生了什么。
原料
基本必需配置
任意文本編輯器(可以用來copy文中出現的代碼)
GCC(我們需要用GCC來編譯C源代碼,并以GCC的規則來講解,其它編譯器產生的結果可能會不同)
額外建議配置
類UNIX的環境(Linux與Mac等皆可,筆者是Mac)
實驗
源代碼
我們擁有swapValue.c與swapAddr.c兩份源代碼,作為研究swap原理的基礎,內容分別如下:
// swapValue.c
void swapValue(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
void fun()
{
int a = 2;
int b = 3;
swapValue(a, b);
}
// swapAddr.c
void swapAddr(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void fun()
{
int a = 2;
int b = 3;
swapAddr(&a, &b);
}
代碼內容很簡單,分別是用傳值和傳地址兩種方式實現swap,并都在fun函數中調用swap。
使用匯編器
啟動命令行窗口,針對上述兩份源代碼進行匯編,輸入如下命令:
gcc -S swapValue.c
gcc -S -O1 swapAddr.c
第二行多了一個-O1參數是為了讓匯編代碼更加便于閱讀。之后得到swapValue.s與swapAddr.s兩份匯編代碼。
分析匯編代碼
swapValue.s
我們首先分析swapValue.s,撇去次要部分后,我們關注如下內容:
_swapValue: ## @swapValue
...
...
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %esi
movl %esi, -12(%rbp)
movl -8(%rbp), %esi
movl %esi, -4(%rbp)
movl -12(%rbp), %esi
movl %esi, -8(%rbp)
...
...
_fun: ## @fun
...
...
movl $2, -4(%rbp)
movl $3, -8(%rbp)
movl -4(%rbp), %edi
movl -8(%rbp), %esi
callq _swapValue
...
...
大家不必去理解匯編代碼的含義,只需要理解筆者的講解即可。可以看到匯編代碼分為_fun和_swapValue兩個部分,與C源碼中兩個函數是對應的。
注意:在匯編中我們把函數改用過程來稱呼。
_fun過程
對于_fun過程,我們可以看到參數2和參數3被最終分別傳遞到了寄存器%edi和%esi中。隨后調用了_swapValue子過程。
簡而言之就是_fun過程將兩個實參存放在兩個寄存器中,然后調用_swapValue子過程。
在x86架構中,上述兩個寄存器是專門用來向函數傳遞參數的,%edi負責傳遞第一個參數,%esi負責傳遞第二個參數。
_swapValue過程
可能是GCC優化問題,匯編代碼拐彎抹角地實現了一個實際上很簡單的操作。
上文有提到:兩個參數存放在寄存器%edi和%esi中。這段代碼首先把兩個參數分別復制到函數的棧內存中,即把%edi復制到-4(%rbp)中,把%esi復制到-8(%rbp)中,通過棧內存來存放局部變量。
隨后拐彎抹角地交換了-4(%rbp)與-8(%rbp)內部的值。可以看到:由于兩個參數一開始就被復制,函數操作的一直都是這份副本。于是,這就是傳值操作無法切實修改參數值的原因。
swapAddr.s
再來看看swapAddr.s,其中_fun過程沒有特別的變化,區別集中在_swapAddr過程。
_swapAddr: ## @swapAddr
...
movl (%rdi), %eax
movl (%rsi), %ecx
movl %ecx, (%rdi)
movl %eax, (%rsi)
...
...
_fun: ## @fun
...
...
movl $2, -4(%rbp)
movl $3, -8(%rbp)
leaq -4(%rbp), %rdi
leaq -8(%rbp), %rsi
callq _swapAddr
...
...
從外觀上,可以看到_swapAddr中寄存器的操作,相比之前多了一對圓括號。(%rdi)與(%rsi)互相交換內容。
這對圓括號就是傳地址的奧秘所在,該操作統稱為間接尋址。
之前寄存器中存放的就是真實的數據,操作時直接取出寄存器中的內容即可。而這里,寄存器中存放的數據不能直接使用,它是一個索引(地址),先取出這個索引,然后去內存中與該索引相對應的位置處取出數據。有點像圖書館中根據書籍的編號去找書。
再仔細想想,這個理念與C語言中的指針是不是很像?沒錯,指針的底層實現就是它!
因此,由于內存中的地址是唯一對應的,因此在_swapAddr中我們就直接修改了兩個參數地址處的值,于是兩個參數也就完成了數據交換。
總結
以上就是對于傳值與傳地址的講解。普通的變量就是保存一個數值而已,而指針是一種保存變量地址的變量,它的第一層含義是地址,第二層含義是根據該地址去取值。
指針常常是表達某個計算的唯一途徑,而且可以生成更加高效緊湊的代碼。例如字符串復制函數,關鍵代碼若用指針只需如下:
char * strcpy(char *dest, char *src)
{
char *ret = dest;
while ((*dest++ = *src++))
;
return ret;
}
正是有了指針,很多高級的操作才成為了可能,宏偉的程序才得以構建。
希望本文對大家有所幫助,感謝閱讀,歡迎分享~
總結
以上是生活随笔為你收集整理的c语言 swap交换函数_重审C中老生常谈的swap函数交换数值的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 根据二叉树前序遍历和中序遍历重建二叉树
- 下一篇: 回溯法模板(矩阵中操作)