C語言陣列複製完整指南|memcpy、for迴圈、strcpy 差異與使用方法說明

目次

1. 前言

C 語言中「陣列複製」的重要性

在使用 C 語言進行程式設計時,常會遇到想要 將陣列內容複製到另一個陣列的情況。例如,想要備份資料,或是為了暫時的處理而需要將值保留在其他變數中等。 然而,與高階語言相比,C 語言在記憶體操作方面的支援較少,陣列的複製也不會自動執行,因此必須手動撰寫複製程式。更糟的是,如果方法寫錯,可能會導致「非預期的行為」或「記憶體破壞」等嚴重錯誤。 因此,了解正確的複製方法,安全且有效率地操作陣列,是學習 C 語言時極為重要的技能之一。

對陣列複製感到困惑的人很多

實際上,以「C 語言 陣列 複製」為關鍵字搜尋的人相當多,足見需求之高。
  • memcpy 就能瞬間完成複製?
  • strcpy 有何不同?
  • for文 逐一複製較安全?
  • 如何寫出使用指標的複製?
為了回答這些疑問,本文將清晰說明 C 語言中陣列複製的基礎到應用

本文可學習的內容

閱讀此頁面,即可獲得以下知識。
  • C 語言中陣列的基本概念
  • 陣列複製的多種方法,以及各自的優點與注意事項
  • 字串(char 型陣列)複製時的技巧與陷阱
  • 以 Q&A 形式說明常見疑問
即使是 C 語言初學者也能輕鬆理解,我們將附上範例程式碼細緻說明。從下一節開始,先來看看陣列的基礎吧。

2. 陣列的基本概念

什麼是陣列?

在 C 語言中,陣列(Array)是指 用於連續儲存相同資料型別的元素的變數。例如,為了儲存 5 人的分數,與其定義 5 個 int 型變數,不如使用一個陣列來統一處理。
int scores[5];
這樣定義的陣列,可以從第 0 個索引依序指定來存取。
scores[0] = 80;
scores[1] = 75;
scores[2] = 90;
scores[3] = 60;
scores[4] = 85;
此處將整數賦值給 scores[0]scores[4] 這 5 個元素。請注意索引是從 0 開始。

陣列的初始化方法

陣列也可以在宣告時進行初始化。初始化是指 在建立陣列的同時賦值
int scores[5] = {80, 75, 90, 60, 85};
這樣寫的話,陣列的每個元素會依序被賦值。另外,也可以省略陣列的大小。
int scores[] = {80, 75, 90, 60, 85};  // 元素數會自動判斷為 5
相反地,即使明確指定元素數,若值不足,剩餘的元素會自動以 0 初始化。
int scores[5] = {80, 75};  // scores[2]~scores[4] 會變成 0

了解陣列的記憶體結構

在 C 語言中,陣列的元素會在記憶體上連續配置。此特性使得可以使用 for 迴圈或指標來有效操作。 例如以下程式碼,會依序顯示陣列的所有元素。
for (int i = 0; i < 5; i++) {
    printf("%d
", scores[i]);
}
這樣了解陣列的基本結構,對於後面提到的「複製處理」也有很大關係。

3. 陣列的複製方法

C 語言中,無法使用賦值運算子(=)一次性複製陣列。以下的程式碼會產生編譯錯誤。
int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a;  // 錯誤:陣列之間不可賦值
因此,要複製陣列,需要明確地逐一複製元素的方法或是使用標準函式庫函式的方法。此處介紹三種代表性的做法。

使用 for 迴圈的逐元素複製

最基本且安全的方法是,使用 for 迴圈 逐一複製元素的方法
#include <stdio.h>

int main() {
    int src[5] = {10, 20, 30, 40, 50};
    int dest[5];

    for (int i = 0; i < 5; i++) {
        dest[i] = src[i];
    }

    // 顯示複製結果
    for (int i = 0; i < 5; i++) {
        printf("%d ", dest[i]);
    }

    return 0;
}
此方法的優點是「容易掌握陣列大小,且易於控制」這點。安全性高,也推薦給初學者。

memcpy 函式的高速複製

如果想更有效率地複製陣列,可以使用包含於標準函式庫 <string.h>memcpy 函式
#include <stdio.h>
#include <string.h>

int main() {
    int src[5] = {1, 2, 3, 4, 5};
    int dest[5];

    memcpy(dest, src, sizeof(src));  // 複製 src 的位元組數

    for (int i = 0; i < 5; i++) {
        printf("%d ", dest[i]);
    }

    return 0;
}

memcpy 的使用要點:

  • 第1引數:目的地指標
  • 第2引數:來源指標
  • 第3引數:要複製的位元組數(注意!不是元素數)

注意事項:

  • 若來源與目的地的陣列大小不同,可能會導致緩衝區溢位,因此務必確認大小。
  • 若記憶體重疊則不可使用(於下一節說明)。

memmove 的差異與使用時機

memcpy 類似的函式有memmove。兩者的差異在於「當來源與目的地重疊時的行為」。
  • memcpy用於記憶體不重疊的情況。若重疊則行為未定義。
  • memmove即使重疊也能安全複製

使用範例:

char str[] = "ABCDE";

// 從第2個字元起,覆寫至第1個字元(有重疊)
memmove(str + 1, str, 4);
str[5] = ' ';  // null 終止

printf("%s
", str);  // 輸出:AABCD

使用時機的基本規則:

使用情況建議函式
記憶體不重疊memcpy
可能重疊memmove
在陣列操作中通常使用 memcpy 已足夠,但在字串操作等需要移動陣列部分的情況下應使用 memmove

4. 字串(char 陣列)的複製

在 C 語言中,字串被視為char型的陣列,需要注意它與數值陣列稍有不同。字串複製有專用的函式,memcpy 的二進位複製不同,「包含終止字元一起複製」的特點。

strcpy函式複製字串

C 語言的標準函式庫 <string.h> 中的 strcpy 函式,是一個會將字串複製至空字元(null 終止)為止的便利函式。
#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello, world!";
    char dest[50];

    strcpy(dest, src);

    printf("複製結果:%s
", dest);

    return 0;
}
此程式碼將 src 的字串(包含終止字元)複製到 dest

注意事項:

  • dest的大小若太小會導致緩衝區溢位,因此需要確保足夠的大小。
  • 被複製的字元數取決於src的長度。

strncpy安全複製

strcpy 的替代函式是strncpy。此函式的規格是「僅複製指定的字元數量」,因此安全性較高。
#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "C language";
    char dest[10];

    strncpy(dest, src, sizeof(dest) - 1);  // 保留最後 1 位元
    dest[9] = ' ';  // 為了保險明確設定終止字元

    printf("複製結果:%s
", dest);

    return 0;
}

strncpy的特性:

  • 僅複製指定長度的字元。
  • 若來源較短,剩餘部分會以NULL填充(視環境而定)。
  • 終止字元不一定會自動附加,需自行明確設定較安全。

處理日文(多位元組字串)時的注意事項

處理日文等多位元組字元(UTF-8、Shift-JIS 等)時,若使用 strcpystrncpy 截斷中間的位元組,會導致字元亂碼顯示錯誤。 例如只複製「こんにちは」的 3 個位元組,會出現不完整的情況。此時需要考慮使用能以字元為單位處理的函式庫,或使用寬字元(wchar_t)。

字串複製的最佳實踐總結

函式名稱特性注意事項
strcpy複製至終止字元必須確認緩衝區大小
strncpy可僅複製指定長度終止字元可能不保證
memcpy可複製任意位元組列二進位複製。非字串用途
strdup為複製的字串分配新記憶體(非標準)使用後需free

5. 陣列複製時的注意事項

陣列的複製看似簡單的處理,但如果未正確處理,會產生重大錯誤或安全漏洞的風險。本節將說明在 C 語言中陣列複製時特別需要注意的要點。

注意緩衝區溢位

最常見的錯誤是,將資料寫入超過目標陣列大小的「緩衝區溢位」。

範例:危險的複製處理

char src[] = "This is a long string.";
char dest[10];

strcpy(dest, src);  // dest的大小被超過複製 → 可能導致記憶體破壞
此類程式碼會引發對不正確記憶體區域的存取,導致程式崩潰或成為漏洞的原因。

對策:

  • 使用 strncpymemcpy務必限制大小
  • 不要忘記手動加入終止字元。
  • 以常數或巨集管理陣列大小。

正確掌握複製目標的大小

使用 memcpymemmove 複製時,需要以「位元組數」而非元素數指定。

安全的大小指定範例:

int src[5] = {1, 2, 3, 4, 5};
int dest[5];

memcpy(dest, src, sizeof(src));  // 指定 src 全體的位元組數
如是使用 sizeof(src),即可自動取得陣列整體的大小(位元組數),因此安全。 但在函式參數中接收陣列時,sizeof 不會如預期運作(因為陣列會退化為指標),需特別注意。

指標操作時的注意事項

在 C 語言中,陣列常被視為指標,錯誤的指標操作可能會破壞記憶體

常見的錯誤範例:

int *src = NULL;
int dest[5];

memcpy(dest, src, sizeof(dest));  // 指定 NULL 指標為來源 → 程式崩潰

要點:

  • 檢查指標是否指向有效位址,執行 NULL 檢查
  • 在記憶體配置(如 malloc)後進行複製時,確認配置大小與複製大小的一致性

當來源與目的區域重疊時的對策

如前所述,memcpy 不支援重疊記憶體區域的複製。若要將陣列的一部份複製到其他位置,應使用 memmove,這是原則。
char buffer[100] = "example";

// 在自身內部移動部分字串
memmove(buffer + 2, buffer, 4);  // 安全的重疊複製

陣列大小的定義與管理

為了執行安全的複製處理,統一管理陣列大小是有效的。如下以巨集定義,可提升程式碼的可維護性與安全性。
#define ARRAY_SIZE 10

int arr1[ARRAY_SIZE];
int arr2[ARRAY_SIZE];

memcpy(arr2, arr1, sizeof(arr1));

安全陣列複製的總結

  • 複製時需正確掌握大小(位元組數)
  • 選擇 strncpymemmove 等安全函式。
  • 始終確認來源與目的的大小一致性
  • 特別注意指標操作,執行 NULL 檢查與邊界檢查。

6. FAQ(常見問題)

C 語言中陣列的複製,從初學者到中級者常常疑惑的要點以 Q&A 形式說明。正確理解細微差異與規格,亦能預防錯誤與提升程式碼品質

Q1. memcpymemmove 的差異是什麼?

A. 當記憶體區域重疊時,操作的安全性不同。
比較項目memcpymemmove
對重疊的安全性×(可能產生未定義行為)◎(內部使用暫存緩衝區處理)
處理速度高速(開銷少)稍慢(有安全措施)
用途陣列的完整複製等同一陣列內的資料移動等
補足:如果確定不會重疊,使用 memcpy 即可,但若不確定,選擇 memmove 較安全。

Q2. strcpystrncpy 的差異與使用方式是?

A. 因為在安全性與彈性之間有取捨,需要分別使用。
  • strcpy(dest, src) → 複製至 src 的終端(\\0)為止的全部。但若 dest 太小,會有緩衝區溢位的危險
  • strncpy(dest, src, n) → 只會複製至指定的最大位元組數 n安全性較高,但不一定會自動加上終止字元
建議的使用方式:
  • 如果確定大小足夠,則使用 strcpy
  • 安全第一、處理不特定字串時,使用 strncpy 並手動加上終止字元

Q3. 傳遞陣列作為函式參數時有什麼注意事項?

A. 陣列會退化為指標,因而無法使用 sizeof 取得大小。
void copy_array(int arr[], int size) {
    printf("%zu
", sizeof(arr));  // ← 會是指標的大小(例如:8)
}
如此一來,無法取得陣列的實際大小,因此在傳遞給函式時,基本上需要同時傳遞大小作為參數
void copy_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        // 處理內容
    }
}

Q4. 使用 memcpy 複製結構體陣列會有問題嗎?

A. 基本上是可行的對於「含有指標的結構體」需特別注意。 memcpy二進位單位的複製方式進行,若結構體中含有指標,則指標所指向的實際資料不會被複製

例(淺層複製):

typedef struct {
    char *name;
    int age;
} Person;

Person a[3];
Person b[3];

memcpy(b, a, sizeof(a));  // 只會複製指標本身(指向的資料會共享)
在此情況下,可能會發生資料的雙重釋放或不一致。對策是需要對指標部分分別使用 malloc + strcpy 等方式進行深層複製

Q5. 想一次性複製陣列,但每次都寫 for 迴圈很麻煩。能否製作共用函式?

A. 可以,將其函式化可提升重用性,程式碼也會更簡潔。
void copy_int_array(int *dest, int *src, int size) {
    for (int i = 0; i < size; i++) {
        dest[i] = src[i];
    }
}
如此一來,針對不同型別與用途事先製作通用的複製函式,即可提升開發效率與可讀性。

7. 總結

在本文中,我們對 C 語言中的「陣列複製」進行了從基礎到應用的系統性解說。最後,回顧重要要點,並針對實務活用進行總結。

陣列複製的基本是「安全性與目的的明確化」

在 C 語言中,無法直接以=來賦值陣列。因此,進行複製需要明確的處理
  • for 迴圈:最基本且易於理解。需要明確指定陣列大小。
  • memcpy:可在二進位層面高速複製。需注意尺寸錯誤。
  • memmove:在來源與目的地重疊時使用。
  • strcpy / strncpy:用於字串(char 陣列)的函式。需考慮安全性而加以區分使用。

安全的複製離不開「尺寸管理」

  • 陣列尺寸的溢位(超過複製)會成為程式崩潰或漏洞的原因。
  • 利用 sizeof()掌握正確的位元組數是很重要的。
  • 在函式中處理陣列時,需了解它會退化為指標,並且將尺寸也作為參數傳遞。

也要了解常見的陷阱

  • strncpy 雖然安全,但可能不會自動加上終止字元,因此請養成手動補上的習慣。
  • 包含結構體指標的陣列,使用 memcpy 可能無法正確複製。
  • 在處理多位元組字串(如日文等)時,亦需注意字元數與位元組數的差異

為了在實務中活用

  • 若在專案中頻繁發生陣列複製,建立專用的工具函式會很方便。
  • 複製處理是易於測試的單元,因此建議透過單元測試等方式確認其安全性。

最後

C 語言因為是低階語言,即使是陣列的複製也相當深奧。然而,只要掌握本次介紹的基礎知識與技巧,就能在各種情境中靈活運用。 請務必以本文為參考,掌握「正確且安全的複製處理」,製作更具可靠性的 C 語言程式。