C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?小樂爸爸2020-02-26 09:20:48

1、如果你結構體成員都是基礎物件(比如int,char等),沒有指標型別物件,直接使用等號賦值即可。

2、如果有指標物件,需要深複製。

3、結構體過於複雜時,自己實現一個複製函式。

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?IT劉小虎2019-07-10 21:13:04

謝邀。

這個問題和我之前發的文章有些相似,上週我在我的C語言學習圈子裡簡要介紹了一個小竅門,粗略來說就是使用C語言結構體的賦值語法,代替memcpy()語句,以精簡程式碼,大致如下圖所示:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

有讀者看到後,認為C語言結構體的賦值並不等價於 memcpy,也有朋友評論說 b=a 是“淺複製”,還有讀者提到結構體賦值效率沒有memcpy高,那麼 b = a 語句被執行後,究竟發生了什麼呢?

編寫測試C語言程式碼

得到答案最簡單直接的方法就是實驗,因此這裡給出一段較為完整的C語言程式碼,用於測試結構體的賦值語句,如下所示。為了討論主題,下面C語言程式碼比較精簡:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

上面這段C語言程式碼很簡單,main() 函式定義了 3 個結構體變數 a, b, c,其中 a 被初始化為 {3, 5},並透過賦值語句複製給 b,memcpy() 複製給 c。考察 a,b,c 佔用的記憶體裡的值,從最終“複製效果”上分析賦值語句和memcpy()的異同。

檢視記憶體值

檢視上述C語言程式中的變數 a, b, c 的值方法很多,最直接的方法就是使用 printf() 函式逐位元組列印,不過這樣就略顯繁瑣了,使用 GDB 工具除錯C語言程式更簡單些。

首先,輸入 gcc t。c -g 編譯上述C語言程式碼,得到可執行檔案 a。out。接著,就可以使用 gdb 除錯了:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

首先在 main() 函式處下斷點,然後輸入 run 命令讓C語言程式執行起來:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

可以發現程式停在第 10 行了,此時變數 a,b, c 還沒有被賦值或者 memcpy。我們先看一下結構體 s 的 size,可以直接在 gdb 環境檢視:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

發現 sizeof(struct s) 等於 16,這主要是因為C語言編譯器為了提升效率,對結構體 s 的兩個成員做了記憶體對齊處理。所以,雖然 char 型的 c 成員實際上只需 1 個位元組記憶體空間,但是因為成員 l 佔用 8 位元組記憶體空間,所以編譯器在 c 後面預留了 7 個位元組。

讀者 @Romi1984 認為 C語言結構體賦值複製和 memcpy 複製不等價,因為“賦值的話,對齊位元組不會複製”。他的意思應該是 c 後面預留的 7 個位元組不會被複製,那是不是如此呢?在執行 b =a; 語句之前,我們先來檢視 a,b,c 在記憶體裡的值:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

能夠看出,此時變數 a,b,c 的記憶體值並不完全相同。輸入 next 命令,使C語言程式執行到第 16 行,也即 return 0; 語句處,此時賦值語句以及 memcpy 語句都被執行完畢,再檢視 a,b,c 的記憶體值,得到如下輸出:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

發現變數 a, b, c 的值完全相同,包括結構體 s 的 c 成員後記憶體對齊的 7 個位元組,這說明讀者 @Romi1984 說的“對齊位元組不會被複製”是不準確的,至少就本例而言,C語言結構體 s 的賦值複製和 memcpy 複製效果上是等價的。

效率問題

雖然透過 gdb 檢視記憶體值,我們發現C語言結構體的賦值複製和 memcpy 複製效果是等價的,但是,讀者 @quser225816904 認為,這兩種方式的效率是不一樣的。

那究竟是否如此呢?得到答案最直接的辦法就是衡量這兩條語句的執行時間。不過由於這一“執行時間”很短,難以計量,我們採取其他方法:輸入下面的命令,檢視C語言程式的彙編程式碼。

# objdump -dS

a。out

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

從C語言程式的彙編程式碼可以看出,b = a; 和 memcpy() 語句都是 4 條 mov 語句,這說明兩種複製方式的效率相差無幾,所以讀者 @quser225816904 的說法也是不準確的。另外,從C語言程式的彙編程式碼也能更直觀的看出 b = a; 和 memcpy() 是等價的。

讀者也可以透過多次執行 b = a 和 memcpy 語句,對比兩種複製方式的效率。

“深複製”和“淺複製”

前面兩位讀者分別從執行效果和執行效率兩個角度質疑了C語言結構體賦值複製和memcpy複製的等價性,也有讀者認為賦值複製只是“淺複製”,那麼究竟是否如此呢?

首先,先要明白“淺複製”和“深複製”概念,這兩個概念 Java,C++,js 等程式語言程式設計師應該比較熟悉,在C語言中倒是不怎麼常提。細究這兩個概念的區別並不是本文的重點,所以這裡粗略的對“淺複製”和“深複製”做如下區分,對於把變數 a 複製給 b:

如果複製後,b 的內容完全等於 a,並且兩個變數在記憶體中是獨立的,則稱此次複製為“深複製”。如果靠背後,只是透過 b 能夠訪問 a 中的內容,a 的內容改變時,b 的“內容”也隨之改變,則稱此次複製為“淺複製”。

這樣看來,就本例而言,b = a;顯然是一次“深複製”,因為 a, b 在記憶體中彼此獨立,並且複製後,b 的內容和 a 的內容完全相同。那C語言的結構體賦值複製一定是“深複製”嗎?我們將結構體 s 新增一個指標成員 buf:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

對 a 的初始化也做相應修改,相關C語言程式碼如下,請看:

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

為了討論主題,上述C語言程式碼沒有做錯誤處理。現在 b = a; 還是“深複製”嗎?讀者如果做了實驗,應該會發現,b 的 buf 成員本身在記憶體中的確獨立於 a 的 buf 成員,但是它指向的記憶體卻與 a 的 buf 成員指向的記憶體是同一塊,所以這時 b = a; 不再是純粹的“深複製”了。

小結

本節主要討論了C語言結構體的賦值語法可以用於複製,並針對之前讀者的幾個典型問題做了較為詳細的例項探討。不過,C語言是一門非常靈活的程式語言,可能同樣的一條語句,在不同的環境下執行結果是不一樣的,這一點本文最後的討論就是一個例項。應該明白,本文舉的例子僅是為了拋磚引玉,展示遇到問題該如何分析的方法,學習C語言,應該樂於做實驗嘗試才對。

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?菸酒僧x2019-07-11 09:09:10

mov ecx,sizeof(STRUCT)

lea esi,lpS

lea edi,lpD

cli

rep movsb

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?Bendanhuo2019-07-25 23:54:36

賦值的話直接用等號就行了吧。

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?三袋大菠蘿2019-07-11 08:30:15

如果一個結構體是4位元組對齊,可以把結構體看做整數陣列,然後成員按整數賦值,這樣每次讀取4位元組會快一些。

同理,在64位CPU上,每次讀取8位元組,能達到較好的效率。

在C++中結構體過載=運算子,可以實現各種可能的操作。

而memcpy是按位元組複製,是一種較低效能的實現。

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?龐娉愷2020-02-26 00:06:16

memcpy已經被建議禁止使用,應該用memcpy_s。

巢狀結構體需要用deep copy。

C語言中複製一個結構體只能使用memcpy的方法嗎?感覺有些麻煩,有別的方法嗎?永恆之存在2019-07-11 09:45:18

可以定義結構體型別,然後跟普通變數一樣直接賦值就行了,但是跟memcpy的效果是一樣的,效率比較低,最好還是用指標傳遞引數,效率高多了。