มีการจัดรูปแบบอาร์เรย์หลายมิติในหน่วยความจำอย่างไร


185

ใน C ฉันรู้ว่าฉันสามารถจัดสรรอาเรย์สองมิติแบบไดนามิกบนฮีปโดยใช้โค้ดต่อไปนี้:

int** someNumbers = malloc(arrayRows*sizeof(int*));

for (i = 0; i < arrayRows; i++) {
    someNumbers[i] = malloc(arrayColumns*sizeof(int));
}

เห็นได้ชัดว่าสิ่งนี้สร้างอาร์เรย์พอยน์เตอร์หนึ่งมิติไปยังอาร์เรย์จำนวนเต็มหนึ่งมิติที่แยกจากกันและ "ระบบ" สามารถหาสิ่งที่ฉันหมายถึงเมื่อฉันขอ:

someNumbers[4][2];

แต่เมื่อฉันประกาศอาร์เรย์ 2 มิติแบบคงที่ดังในบรรทัดต่อไปนี้ ... :

int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];

... โครงสร้างที่คล้ายกันนี้ถูกสร้างขึ้นบนสแต็กหรือเป็นรูปแบบอื่นอย่างสมบูรณ์หรือไม่ (เช่นเป็นตัวชี้อาร์เรย์ 1D หรือไม่ถ้าไม่ใช่มันคืออะไรและการอ้างอิงถึงมันคิดออกได้อย่างไร)

นอกจากนี้เมื่อฉันพูดว่า "ระบบ" อะไรคือสิ่งที่ต้องรับผิดชอบในการหาสิ่งนั้น เคอร์เนล หรือคอมไพเลอร์ C เรียงลำดับขณะคอมไพล์หรือไม่?


8
ฉันจะให้มากกว่า +1 ถ้าทำได้
Rob Lachlan

1
คำเตือน : ไม่มีอาเรย์ 2 มิติในรหัสนี้!
ซื่อสัตย์เกินไปสำหรับไซต์นี้

@toohonestforthissite แน่นอน หากต้องการขยาย: การวนลูปและการโทรmalloc()ไม่ส่งผลให้มีอาร์เรย์ N-Dim . มันส่งผลให้อาร์เรย์ของพอยน์เตอร์ [ไปยังอาร์เรย์ของพอยน์เตอร์ [... ]] เพื่อแยกอาร์เรย์หนึ่งมิติอย่างสมบูรณ์ ดูการจัดสรรอาร์เรย์หลายมิติอย่างถูกต้องเพื่อดูวิธีการจัดสรรอาร์เรย์TRUE N มิติ
Andrew Henle

คำตอบ:


145

อาเรย์สองมิติแบบสแตติกดูเหมือนว่าอาร์เรย์ของอาเรย์ - มันเพิ่งจะวางในหน่วยความจำต่อเนื่อง อาเรย์นั้นไม่เหมือนกับพอยน์เตอร์ แต่เป็นเพราะคุณมักจะสามารถใช้มันแทนกันได้มากมันอาจทำให้สับสนได้ในบางครั้ง คอมไพเลอร์ติดตามอย่างถูกต้อง แต่ที่ทำให้ทุกอย่างเข้าแถวกัน คุณต้องระวังด้วยอาร์เรย์ 2D แบบคงที่อย่างที่คุณพูดถึงเพราะถ้าคุณพยายามส่งหนึ่งไปยังฟังก์ชั่นรับint **พารามิเตอร์สิ่งเลวร้ายจะเกิดขึ้น นี่คือตัวอย่างรวดเร็ว:

int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}};

ในหน่วยความจำมีลักษณะเช่นนี้:

0 1 2 3 4 5

เหมือนกับ :

int array2[6] = { 0, 1, 2, 3, 4, 5 };

แต่ถ้าคุณพยายามส่งผ่านarray1ไปยังฟังก์ชันนี้:

void function1(int **a);

คุณจะได้รับคำเตือน (และแอปจะไม่สามารถเข้าถึงอาร์เรย์ได้อย่างถูกต้อง):

warning: passing argument 1 of function1 from incompatible pointer type

เพราะอาร์เรย์ 2D int **ไม่ได้เช่นเดียวกับ การสลายตัวของอาเรย์โดยอัตโนมัติไปยังตัวชี้จะไปเพียง "ระดับหนึ่งลึก" เพื่อพูด คุณต้องประกาศฟังก์ชันเป็น:

void function2(int a[][2]);

หรือ

void function2(int a[3][2]);

ที่จะทำให้ทุกอย่างมีความสุข

แนวคิดเดียวกันนี้รวมถึงอาร์เรย์n -dimensional การใช้ประโยชน์จากธุรกิจประเภทนี้ในแอปพลิเคชันของคุณโดยทั่วไปจะทำให้เข้าใจได้ยากขึ้นเท่านั้น ดังนั้นระวังให้ดี


ขอบคุณสำหรับคำอธิบาย ดังนั้น "void function2 (int a [] [2]);" จะยอมรับ 2D ทั้งแบบคงที่และแบบไดนามิกหรือไม่ และฉันคิดว่ามันยังคงเป็นแนวปฏิบัติที่ดี / จำเป็นที่จะต้องผ่านความยาวของอาเรย์เช่นกันหากมิติแรกนั้นเหลือ [[]?
Chris Cooper

1
@ Chris ฉันไม่คิดอย่างนั้น - คุณจะมีเวลายากที่จะทำให้ C swizzle เป็นสแต็ก - หรืออาเรย์ที่ถูกจัดสรรทั่วโลกให้เป็นพอยน์เตอร์
Carl Norum

6
@JasonK - ไม่ อาร์เรย์ไม่ใช่ตัวชี้ จัดเรียง "ผุ" เป็นตัวชี้ในบริบทบางอย่าง แต่ไม่เหมือนกันทั้งหมด
Carl Norum

1
เพื่อความชัดเจน: ใช่คริส "มันยังเป็นแนวปฏิบัติที่ดีที่จะส่งผ่านความยาวของอาร์เรย์" เป็นพารามิเตอร์แยกต่างหากมิฉะนั้นให้ใช้ std :: array หรือ std :: vector (ซึ่ง C ++ ไม่ใช่ C เก่า) ฉันคิดว่าเราเห็นด้วย @CarlNorum ทั้งแนวคิดสำหรับผู้ใช้ใหม่และในทางปฏิบัติเพื่ออ้าง Anders Kaseorg ใน Quora:“ ขั้นตอนแรกของการเรียนรู้ C คือการเข้าใจว่าพอยน์เตอร์และอาร์เรย์เป็นสิ่งเดียวกัน ขั้นตอนที่สองคือการทำความเข้าใจว่าพอยน์เตอร์และอาร์เรย์ต่างกันอย่างไร”
Jason K.

2
@JasonK "ขั้นตอนแรกของการเรียนรู้ C คือการเข้าใจว่าพอยน์เตอร์และอาร์เรย์เป็นสิ่งเดียวกัน" - คำพูดนี้ผิดมากและขีปนาวุธ! เป็นขั้นตอนที่สำคัญที่สุดที่จะเข้าใจว่าพวกเขาไม่เหมือนกัน แต่อาร์เรย์นั้นถูกแปลงเป็นตัวชี้ไปยังองค์ประกอบแรกสำหรับผู้ประกอบการส่วนใหญ่! sizeof(int[100]) != sizeof(int *)(เว้นแต่คุณจะพบแพลตฟอร์มที่มี100 * sizeof(int)ไบต์ / intแต่นั่นเป็นสิ่งที่แตกต่าง
ซื่อสัตย์เกินไปสำหรับไซต์นี้

85

คำตอบจะขึ้นอยู่กับความคิดที่ว่า C ไม่ได้จริงๆมี 2D อาร์เรย์ - มันมีอาร์เรย์ของอาร์เรย์ เมื่อคุณประกาศสิ่งนี้:

int someNumbers[4][2];

คุณกำลังขอsomeNumbersให้เป็นอาร์เรย์ขององค์ประกอบที่ 4 ซึ่งแต่ละองค์ประกอบของอาร์เรย์นั้นเป็นประเภทint [2](ซึ่งตัวเองเป็นอาร์เรย์ของ 2intวินาที)

อีกส่วนหนึ่งของตัวต่อคืออาร์เรย์จะถูกวางในหน่วยความจำอย่างต่อเนื่อง หากคุณขอ:

sometype_t array[4];

จากนั้นจะมีลักษณะเช่นนี้เสมอ:

| sometype_t | sometype_t | sometype_t | sometype_t |

( sometype_tวางวัตถุ4 ชิ้นติดกันโดยไม่มีช่องว่างระหว่างกัน) ดังนั้นในของคุณsomeNumbers array-of-arrays ของคุณมันจะมีลักษณะดังนี้:

| int [2]    | int [2]    | int [2]    | int [2]    |

และแต่ละอัน int [2]องค์ประกอบเป็นอาเรย์เองซึ่งมีลักษณะดังนี้:

| int        | int        |

โดยรวมแล้วคุณจะได้รับสิ่งนี้:

| int | int  | int | int  | int | int  | int | int  |

1
การดูเค้าโครงสุดท้ายทำให้ฉันคิดว่า int [] [] สามารถเข้าถึงได้ในรูปแบบ int * ... ใช่ไหม?
Narcisse Doudieu Siewe

2
@ user3238855: ชนิดไม่เข้ากัน แต่ถ้าคุณได้รับตัวชี้ไปยังลำดับแรกintในอาร์เรย์ของอาร์เรย์ (เช่นโดยการประเมินa[0]หรือ&a[0][0]) จากนั้นใช่คุณสามารถชดเชยการเข้าถึงตามลำดับทุกครั้งint)
caf

28
unsigned char MultiArray[5][2]={{0,1},{2,3},{4,5},{6,7},{8,9}};

ในหน่วยความจำเท่ากับ:

unsigned char SingleArray[10]={0,1,2,3,4,5,6,7,8,9};

5

ในการตอบคำถามของคุณ: ทั้งคู่แม้ว่าคอมไพเลอร์กำลังทำการยกของหนัก

ในกรณีที่มีการจัดสรรอาร์เรย์แบบคงที่ "ระบบ" จะเป็นคอมไพเลอร์ มันจะสำรองหน่วยความจำเช่นเดียวกับตัวแปรสแต็คใด ๆ

ในกรณีของอาร์เรย์ malloc'd "The System" จะเป็น implementer ของ malloc (โดยปกติเคอร์เนล) คอมไพเลอร์ทั้งหมดจะจัดสรรเป็นตัวชี้ฐาน

คอมไพเลอร์มักจะจัดการกับประเภทตามที่พวกเขาถูกประกาศให้เป็นยกเว้นในตัวอย่างที่คาร์ลมอบให้ซึ่งมันสามารถใช้งานแทนกันได้ นี่คือเหตุผลที่ถ้าคุณส่งผ่านใน [] [] ไปยังฟังก์ชันมันจะต้องสมมติว่ามันเป็นแบบคงที่การจัดสรรแบบคงที่ซึ่งจะถือว่า ** เป็นตัวชี้ไปยังตัวชี้


@ จอนลิตรฉันจะไม่พูด malloc ว่าจะดำเนินการโดย kernel แต่ libc ด้านบนของวิทยาการเคอร์เนล (เช่น BRK)
มานูเอล Selva

@ManuelSelva: ที่ไหนและวิธีmallocการใช้งานไม่ได้ระบุไว้โดยมาตรฐานและออกจากการดำเนินการตามด้วย สิ่งแวดล้อม สำหรับสภาพแวดล้อมอิสระมันเป็นทางเลือกเช่นเดียวกับทุกส่วนของไลบรารีมาตรฐานที่ต้องการฟังก์ชั่นการเชื่อมโยง (นั่นคือสิ่งที่ความต้องการจริงส่งผลให้ไม่ใช่ตัวอักษรตามที่มาตรฐานกำหนดไว้) สำหรับสภาพแวดล้อมการโฮสต์ที่ทันสมัยบางอย่างมันขึ้นอยู่กับฟังก์ชั่นเคอร์เนลทั้งสิ่งที่สมบูรณ์หรือ (เช่น Linux) ตามที่คุณเขียนโดยใช้ทั้ง stdlib และเคอร์เนล - พื้นฐาน สำหรับระบบกระบวนการเดียวที่ไม่ใช่หน่วยความจำเสมือนสามารถเป็น stdlib เท่านั้น
ซื่อสัตย์เกินไปสำหรับไซต์นี้

2

สมมติว่าเรามีa1และa2กำหนดและเริ่มต้นเหมือนด้านล่าง (c99):

int a1[2][2] = {{142,143}, {144,145}};
int **a2 = (int* []){ (int []){242,243}, (int []){244,245} };

a1เป็นอาร์เรย์ 2 มิติที่เป็นเนื้อเดียวกันที่มีเค้าโครงต่อเนื่องธรรมดาในหน่วยความจำและนิพจน์(int*)a1จะถูกประเมินเป็นตัวชี้ไปยังองค์ประกอบแรก:

a1 --> 142 143 144 145

a2ถูกเตรียมใช้งานจากอาร์เรย์ 2 มิติที่แตกต่างกันและเป็นตัวชี้ไปยังค่าของชนิดint*เช่นการแสดงออกของการ*a2ประเมินผลการประเมินค่าเป็นประเภทประเภทint*รูปแบบหน่วยความจำไม่จำเป็นต้องต่อเนื่อง:

a2 --> p1 p2
       ...
p1 --> 242 243
       ...
p2 --> 244 245

แม้จะมีเค้าโครงหน่วยความจำที่แตกต่างกันโดยสิ้นเชิงและซีแมนทิกส์การเข้าถึงไวยากรณ์ภาษา C สำหรับนิพจน์การเข้าถึงอาร์เรย์นั้นดูเหมือนกันสำหรับทั้งสองมิติที่เหมือนกันและอาเรย์สองมิติที่แตกต่างกัน:

  • การแสดงออกa1[1][0]จะดึงค่า144ออกมาจากa1อาร์เรย์
  • การแสดงออกa2[1][0]จะดึงค่า244ออกมาจากa2อาร์เรย์

คอมไพเลอร์รู้ว่า access-expression สำหรับa1ทำงานกับชนิดint[2][2]เมื่อ access-expression สำหรับa2ทำงานกับชนิดint**ดำเนินการกับชนิดรหัสแอสเซมบลีที่สร้างขึ้นจะเป็นไปตามความหมายการเข้าถึงที่เหมือนกันหรือต่างกัน

รหัสมักจะล้มเหลวในเวลาทำงานเมื่ออาเรย์ของประเภทint[N][M]เป็นประเภทหล่อและจากนั้นเข้าถึงเป็นประเภทint**เช่น:

((int**)a1)[1][0]   //crash on dereference of a value of type 'int'

1

ในการเข้าถึงอาเรย์ 2 มิติให้พิจารณาแผนที่หน่วยความจำสำหรับการประกาศอาร์เรย์ดังที่แสดงในรหัสด้านล่าง:

    0  1
a[0]0  1
a[1]2  3

ในการเข้าถึงแต่ละองค์ประกอบก็เพียงพอที่จะผ่านอาร์เรย์ที่คุณสนใจในฐานะพารามิเตอร์ไปยังฟังก์ชัน จากนั้นใช้ออฟเซ็ตสำหรับคอลัมน์เพื่อเข้าถึงแต่ละองค์ประกอบแยกกัน

int a[2][2] ={{0,1},{2,3}};

void f1(int *ptr);

void f1(int *ptr)
{
    int a=0;
    int b=0;
    a=ptr[0];
    b=ptr[1];
    printf("%d\n",a);
    printf("%d\n",b);
}

int main()
{
   f1(a[0]);
   f1(a[1]);
    return 0;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.