日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

c语言 swap交换函数_重审C中老生常谈的swap函数交换数值

發布時間:2025/3/15 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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函数交换数值的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。