色婷婷狠狠18禁久久YY,CHINESE性内射高清国产,国产女人18毛片水真多1,国产AV在线观看

C語言中,函數的返回值返回到main函數后,賦予給一個變量,之后就被回收了嗎?還有哪些編程細節需要注意?

夏志豪2年前14瀏覽0評論

根據所用編譯器和CPU的不同,以及返回值數據類型的不同,C語言中的函數返回值可能通過寄存器傳遞,也可能通過棧傳遞。對大多數CPU和編譯器來說,出于性能考慮,能使用寄存器傳遞的,盡量使用寄存器傳遞,只有當寄存器不夠用的時候,才會通過棧傳遞。

針對這兩種情況,我分別舉個x64 + GCC環境下的例子來說明。

通過寄存器傳遞返回值

如下圖中的一段簡單的代碼,返回值是一個有符號整數類型

我們看下x64/GCC下面對應的匯編代碼:

test函數中的

1129: mov $0x2,%eax

便是把返回值2存放到eax寄存器中。而main函數中的

113d: callq 1125 <test>

1142:mov %eax,-0x4(%rbp)

則先調用test函數,然后把返回值從eax中取出,并存放到rbp - 4的地址處,也就是賦值給局部變量a。

通過棧傳遞返回值

下面這個例子中,test()函數返回一個結構體struct result。(注:這里只是為了演示用棧傳遞返回值,實際項目中不建議函數直接返回結構體,可以用結構體指針代替)

(這個例子第一眼看上去會有些許復雜,千萬不要懵逼,匯編代碼不是洪水猛獸,掌握一些基本的匯編代碼對修煉內功、調試問題都是大有裨益的:)

在x64/GCC環境下的匯編代碼如下:

先看main()函數:

我們先看main()函數中調用test()的幾條指令:

ret = test();

11dd: lea -0x50(%rbp), %rax

11e1: mov %rax, %rdi

11e4: mov $0x0, %eax

11e9: callq 1135 <test>

11dd和11e1兩條指令的作用是把棧地址rbp - 0x50存放到rdi寄存器中,我們暫且不去管這個地址是用來做什么的,等看了test()函數之后自然就會明白。后面兩條指令是把eax清零,然后調用test()函數。

test()函數的匯編代碼如下:

test()的匯編看起來是不是有點復雜呢?不要緊張,其實做的事情很簡單,就是給局部變量r分配棧空間,然后對它進行初始化,然后把r的值存放到一個內存地址當中,最后把這個內存地址放到rax寄存器中,并返回出去。我們仔細分析一下:

1139: mov %rdi, -0x28(%rbp)

這條指令是把rdi寄存器的值存放到棧空間rbp - 0x28的地址處。還記得rdi寄存器中存放的是什么嗎?回想一下,在main()函數調用test()函數之前,是不是把一個地址存放到rdi寄存器中了呢?忘了的話,再去看一下。我們先不管這個值用來做什么,只要記得,test()函數把main()函數傳遞過來的一個值存放到了一個棧地址當中。

接下來的這幾條指令,就是對局部變量r進行初始化:

struct result r = {1, 2, 3, 4};

113d: movq $0x1, -0x20(%rbp)

1145: movq $0x2, -0x18(%rbp)

114d: movq $0x3, -0x10(%rbp)

1155: movq $0x4, -0x8(%rbp)

下面就要把r的值返回出去了,我們來看看編譯器是怎么做的,先看這幾條指令:

return r;

115d: mov -0x28(%rbp), %rcx

1161: mov -0x20(%rbp), %rax

1165: mov -0x18(%rbp), %rdx

1169: mov %rax, (%rcx)

116c: mov %rdx, 0x8(%rcx)

115d這條指令,是把棧中rbp-0x28處的值放到rcx寄存器中,還記得這個地址存放的值是什么嗎?對了,就是test()入口處從rdi中取出來的那個值,也就是main()函數通過rdi寄存器傳遞給test()的一個值。然后,1161和1169兩條指令把r.a值存放到rcx寄存器指向的地址處,1165和116c兩條指令把r.b的值存放到rcx寄存器指向的地址再偏移8的位置處。

現在我們再來回過頭想一下,main()函數通過rdi寄存器傳遞給test()函數的那個值是用來做什么的呢?對了,那個值其實就是存放test()函數返回值的那塊內存的地址。

那么記下來的幾條指令就比較容易理解了:

1170: mov -0x10(%rbp), %rax

1174: mov -0x8(%rbp), %rdx

1178: mov %rax, 0x10(%rcx)

117c: mov %rdx, 0x18(%rcx)

1170和1178把r.c存放到rcx + 0x10地址處,1174和117c把r.d存放到rcx + 0x18地址處。

到這里為止,test()函數已經把局部變量struct result r的所有字段的值全部存放到main()函數通過rdi寄存器傳遞給test()的那個內存地址中。

最后,看一下剩下的幾條指令:

1180: mov -0x28(%rbp), %rax

1184: pop %rbp

1185: retq

1180指令把rbp - 0x28處的值rax中,也就是把存放返回值的那塊內存的地址,存放到rax寄存器中,最后返回出去。

到這里,是不是清晰多了呢?我們再來總結一下這個過程:

  • main()函數把一個棧空間中的地址rbp - 0x30通過rdi寄存器傳遞給test()函數
  • test()函數從rdi寄存器中取得這個地址,然后把要返回的值存放到這個地址指向的內存中
  • test()把這個地址存放到rax寄存器中,并返回給main()函數

掌握一定匯編知識的重要性

可能對于很多童鞋來說,匯編語言比較晦澀難懂,難以掌握。確實,作為一個最為接近機器語言的編程語言來說,匯編確實比較晦澀,除了一些做底層系統軟件的童鞋外,日常工作中直接用匯編寫代碼的機會確實不多,但是,這并不意味著掌握匯編語言就毫無用處。

掌握一定的匯編知識,會對整個計算機的原理和體系結構有更深入的理解,很多東西都能夠知其然并知其所以然。尤其那些對底層系統軟件感興趣的童鞋,如BIOS/bootloader、OS內核、設備驅動、編譯器、虛擬機等,匯編語言更是必須要掌握的。有些做上層應用的童鞋,如前端開發等,平時用到匯編的機會不多,但是在調試一些問題的時候,如果能夠了解一些匯編知識,就會如虎添翼,事半功倍。

總之,不管所用的開發語言是C/C++還是Java、Python、PHP、Javascript,不管是做系統軟件開發,還是做前端開發,只要是有志于干程序員這一行當的,掌握一定的匯編,對完善自己的技術知識體系,增強自己調試問題的能力,和對計算機體系結構的理解都大有裨益。

思考題

能堅持讀到這里,我想你已經基本清楚C語言的函數返回值是怎么傳遞的了。

那么,不妨思考一下,C語言的函數參數又是怎么傳遞的呢?