ที่อยู่ของอาร์เรย์นั้นมีค่าเท่ากับมูลค่าใน C อย่างไร


189

ในรหัสต่อไปนี้ค่าของตัวชี้และที่อยู่ของตัวชี้จะแตกต่างกันตามที่คาดไว้

แต่ค่าและที่อยู่ของอาร์เรย์ไม่ได้!

สิ่งนี้จะเป็นอย่างไร

เอาท์พุต

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}

จาก comp.lang.c คำถามที่พบบ่อย: - [ดังนั้นความหมายของ `` เทียบเท่าของพอยน์เตอร์และอาร์เรย์ '' ใน C คืออะไร? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [เนื่องจากอาร์เรย์อ้างอิงการสลายตัวเป็นพอยน์เตอร์ถ้า arr เป็นอาร์เรย์อะไรคือความแตกต่างระหว่าง arr และ & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) หรือไปอ่านส่วนอาร์เรย์และพอยน์เตอร์ทั้งหมด
jamesdlin

3
ผมได้เพิ่มคำตอบกับแผนภาพคำถามนี้สองปีกลับมาที่นี่อะไรsizeof(&array)กลับมา?
Grijesh Chauhan

คำตอบ:


214

ชื่อของอาเรย์มักจะประเมินที่อยู่ขององค์ประกอบแรกของอาเรย์ดังนั้นarrayและ&arrayมีค่าเดียวกัน (แต่ประเภทที่แตกต่างกันดังนั้นarray+1และ&array+1จะไม่เท่ากับถ้าอาร์เรย์มีความยาวมากกว่า 1 องค์ประกอบ)

มีข้อยกเว้นสองข้อสำหรับสิ่งนี้: เมื่อชื่ออาร์เรย์เป็นตัวถูกดำเนินการsizeofหรือไม่พร้อมกัน&(ที่อยู่ - ของ) ชื่อนั้นอ้างถึงวัตถุอาร์เรย์นั้นเอง ดังนั้นsizeof arrayจะให้คุณมีขนาดเป็นไบต์ของอาร์เรย์ทั้งหมดไม่ใช่ขนาดของตัวชี้

สำหรับอาร์เรย์กำหนดให้เป็นก็จะมีประเภทT array[size] T *เมื่อ / หากคุณเพิ่มมันคุณจะได้องค์ประกอบต่อไปในอาร์เรย์

&arrayประเมินที่อยู่เดียวกัน แต่กำหนดนิยามเดียวกันมันจะสร้างตัวชี้ของประเภทT(*)[size]- กล่าวคือเป็นตัวชี้ไปยังอาร์เรย์ไม่ใช่องค์ประกอบเดียว หากคุณเพิ่มพอยน์เตอร์นี้มันจะเพิ่มขนาดของอาร์เรย์ทั้งหมดไม่ใช่ขนาดขององค์ประกอบเดียว ตัวอย่างเช่นด้วยรหัสเช่นนี้:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

เราสามารถคาดหวังว่าตัวชี้ที่สองจะ 16 มากกว่าตัวแรก เนื่องจากโดยทั่วไป% p จะแปลงพอยน์เตอร์เป็นเลขฐานสิบหกจึงอาจมีลักษณะดังนี้:

0x12341000    0x12341010

3
@Alexandre: &arrayเป็นตัวชี้ไปยังองค์ประกอบแรกของอาร์เรย์โดยที่arrayอ้างอิงถึงอาร์เรย์ทั้งหมด ความแตกต่างพื้นฐานยังสามารถสังเกตได้โดยการเปรียบเทียบเพื่อsizeof(array) sizeof(&array)อย่างไรก็ตามโปรดทราบว่าหากคุณส่งผ่านarrayข้อโต้แย้งไปยังฟังก์ชัน&arrayจะมีการส่งผ่านความเป็นจริงเท่านั้น คุณไม่สามารถส่งผ่านอาร์เรย์ตามค่าได้เว้นแต่จะมีการห่อหุ้มภายในstructด้วย
Clifford

14
@Clifford: หากคุณส่งอาร์เรย์ไปยังฟังก์ชันมันจะสลายตัวไปยังตัวชี้ไปยังองค์ประกอบแรกของมันอย่างมีประสิทธิภาพดังนั้น&array[0]จะไม่ผ่าน&arrayซึ่งจะเป็นตัวชี้ไปยังอาร์เรย์ มันอาจจะเป็น nit-pick แต่ฉันคิดว่ามันสำคัญที่จะต้องทำให้ชัดเจน คอมไพเลอร์จะเตือนถ้าฟังก์ชั่นนั้นมีต้นแบบที่ตรงกับประเภทของตัวชี้ที่ส่งผ่านมา
CB Bailey

2
@Jerry Coffin ตัวอย่างเช่น int * p = & a ถ้าฉันต้องการที่อยู่หน่วยความจำของตัวชี้ int ฉันสามารถทำ & p ตั้งแต่ & อาร์เรย์จะแปลงเป็นที่อยู่ของทั้งอาร์เรย์ (ซึ่งเริ่มต้นที่ที่อยู่ขององค์ประกอบแรก) แล้วฉันจะค้นหาที่อยู่หน่วยความจำของตัวชี้อาร์เรย์ได้อย่างไร (ซึ่งเก็บที่อยู่ขององค์ประกอบแรกในอาร์เรย์) มันจะต้องอยู่ที่ไหนสักแห่งในหน่วยความจำใช่มั้ย
John Lee

2
@JohnLee: ไม่ไม่จำเป็นต้องมีตัวชี้ไปยังอาร์เรย์ในหน่วยความจำ int *p = array; int **pp = &p;ถ้าคุณสร้างตัวชี้แล้วคุณสามารถใช้เวลาที่อยู่:
Jerry Coffin

3
@Clifford ความคิดเห็นแรกไม่ถูกต้องทำไมยังคงเก็บไว้? ฉันคิดว่าอาจนำไปสู่การเข้าใจผิดสำหรับผู้ที่ไม่ได้อ่านคำตอบ (@Charles) ต่อไปนี้
Rick

30

นั่นเป็นเพราะชื่ออาร์เรย์ ( my_array) แตกต่างจากตัวชี้ไปยังอาร์เรย์ มันเป็นนามแฝงไปยังที่อยู่ของอาร์เรย์และที่อยู่ของมันถูกกำหนดให้เป็นที่อยู่ของอาร์เรย์เอง

ตัวชี้เป็นตัวแปร C ปกติบนสแต็ก ดังนั้นคุณสามารถใช้ที่อยู่และรับค่าที่แตกต่างจากที่อยู่ภายใน

ฉันเขียนเกี่ยวกับหัวข้อนี้ที่นี่ - โปรดดู


ไม่ควร & my_array เป็นการดำเนินการที่ไม่ถูกต้องเนื่องจากค่าของ my_array ไม่ได้อยู่ในสแต็กมีเพียง my_array [0 ... ความยาว] เท่านั้นใช่หรือไม่ ถ้าอย่างนั้นมันก็สมเหตุสมผล ...
Alexandre

@Alexandre: ฉันไม่แน่ใจว่าทำไมมันถึงได้รับอนุญาต
Eli Bendersky

คุณสามารถระบุที่อยู่ของตัวแปรใด ๆ (หากไม่ได้ทำเครื่องหมายไว้register) ไม่ว่าจะเป็นช่วงเวลาการเก็บข้อมูลใด ๆ : คงที่ไดนามิกหรืออัตโนมัติ
CB Bailey

my_arrayตัวเองอยู่ในกองเพราะmy_array เป็นทั้งอาร์เรย์
caf

3
my_arrayเมื่อไม่ใช่หัวเรื่องของตัวดำเนินการ&หรือsizeofตัวดำเนินการจะถูกประเมินเป็นตัวชี้ไปยังองค์ประกอบแรก (เช่น&my_array[0]) - แต่my_arrayตัวเองไม่ใช่ตัวชี้นั้น ( my_arrayยังคงเป็นอาร์เรย์) ตัวชี้นั้นเป็นเพียง rvalue ชั่วคราว (เช่นกำหนดint a;เป็นเหมือนa + 1) - อย่างน้อยแนวคิดก็คือ "คำนวณตามต้องการ" "คุณค่า" ที่แท้จริงของmy_arrayคือเนื้อหาของอาร์เรย์ทั้งหมด - มันเป็นเพียงการตรึงค่านี้ใน C เป็นเหมือนพยายามที่จะจับหมอกในขวด
caf

28

ใน C เมื่อคุณใช้ชื่อของอาร์เรย์ในนิพจน์ (รวมถึงส่งผ่านไปยังฟังก์ชัน) ยกเว้นว่ามันเป็นตัวถูกดำเนินการของตัวดำเนินการ address-of ( &) หรือตัวsizeofดำเนินการมันจะสลายตัวไปยังตัวชี้ไปยังองค์ประกอบแรก

นั่นคือในบริบทส่วนใหญ่arrayเทียบเท่ากับ&array[0]ทั้งในประเภทและค่า

ในตัวอย่างของคุณmy_arrayมีชนิดchar[100]ที่ decays ไปchar*เมื่อคุณส่งไปยัง printf

&my_arrayมีประเภทchar (*)[100](ตัวชี้ไปยังอาร์เรย์ 100 char) เนื่องจากเป็นตัวถูกดำเนินการไป&แล้วนี่เป็นหนึ่งในกรณีที่my_arrayไม่สลายตัวทันทีไปยังตัวชี้ไปยังองค์ประกอบแรก

ตัวชี้ไปยังอาร์เรย์มีค่าที่อยู่เดียวกับตัวชี้ไปยังองค์ประกอบแรกของอาร์เรย์เป็นวัตถุอาร์เรย์เป็นเพียงลำดับที่ต่อเนื่องกันขององค์ประกอบ แต่ตัวชี้ไปยังอาร์เรย์มีชนิดที่แตกต่างกันไปยังตัวชี้ไปยังองค์ประกอบของ อาร์เรย์นั้น สิ่งนี้มีความสำคัญเมื่อคุณทำการคำนวณทางคณิตศาสตร์ของตัวชี้ทั้งสองประเภท

pointer_to_arrayมีประเภทchar *- เริ่มต้นที่จุดที่องค์ประกอบแรกของอาร์เรย์ที่เป็นสิ่งที่my_arrayสลายตัวไปในการแสดงออก initializer - และ&pointer_to_array มีประเภทchar **(ตัวชี้ไปยังตัวชี้ไปยัง a char)

ของเหล่านี้: my_array(หลังจากสลายตัวไปchar*) &my_arrayและpointer_to_arrayทุกจุดโดยตรงที่ทั้งอาร์เรย์หรือองค์ประกอบแรกของอาร์เรย์และเพื่อให้มีค่าที่อยู่เดียวกัน


3

เหตุผลที่ทำให้my_arrayและ&my_arrayที่อยู่เดียวกันสามารถเข้าใจได้ง่ายเมื่อคุณดูเค้าโครงหน่วยความจำของอาร์เรย์

สมมติว่าคุณมีอาร์เรย์ 10 อักขระ (แทนที่จะเป็น 100 ในรหัสของคุณ)

char my_array[10];

หน่วยความจำสำหรับmy_arrayดูเหมือนว่า:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

ใน C / C ++ อาร์เรย์จะสลายตัวไปยังตัวชี้ไปยังองค์ประกอบแรกในนิพจน์เช่น

printf("my_array = %p\n", my_array);

หากคุณตรวจสอบว่าองค์ประกอบแรกของอาร์เรย์อยู่ที่ไหนคุณจะเห็นว่าที่อยู่ของมันเหมือนกับที่อยู่ของอาร์เรย์:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].

3

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

ได้รับการประกาศระดับโลกเช่นi;[ไม่มีความจำเป็นเพื่อระบุชนิดเพราะทุกอย่างเป็นจำนวนเต็ม / ตัวชี้] จะได้รับการประมวลผลโดยเรียบเรียงเป็น: address_of_i = next_global++; memory[address_of_i] = 0;และงบเหมือนจะได้รับการประมวลผลเป็น:i++memory[address_of_i] = memory[address_of_i]+1;

การประกาศเช่นจะได้รับการประมวลผลarr[10]; address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;หมายเหตุว่าทันทีที่ประกาศว่าได้รับการประมวลผลคอมไพเลอร์ทันทีสามารถลืมเกี่ยวกับarrการเป็นอาร์เรย์ คำสั่งที่ชอบจะได้รับการประมวลผลarr[i]=6; memory[memory[address_of_a] + memory[address_of_i]] = 6;คอมไพเลอร์จะไม่สนใจว่าจะarrแสดงอาร์เรย์และiจำนวนเต็มหรือในทางกลับกัน อันที่จริงมันจะไม่สนใจว่าพวกเขาทั้งสองอาร์เรย์หรือจำนวนเต็มทั้งสอง; มันจะสร้างรหัสอย่างมีความสุขอย่างสมบูรณ์แบบตามที่อธิบายไว้โดยไม่คำนึงว่าพฤติกรรมที่เกิดขึ้นน่าจะมีประโยชน์หรือไม่

หนึ่งในเป้าหมายของภาษาการเขียนโปรแกรม C คือการเข้ากันได้อย่างมากกับ B. ใน B ชื่อของอาร์เรย์ [เรียกว่า "เวกเตอร์" ในคำศัพท์ของ B] ระบุตัวแปรที่ถือตัวชี้ซึ่งเริ่มมอบหมายให้ชี้ไปที่ ไปยังองค์ประกอบแรกของการจัดสรรขนาดที่กำหนดดังนั้นหากชื่อนั้นปรากฏในรายการอาร์กิวเมนต์ของฟังก์ชันฟังก์ชันจะได้รับตัวชี้ไปยังเวกเตอร์ แม้ว่า C จะเพิ่มประเภทอาร์เรย์ "ของจริง" ซึ่งชื่อมีความสัมพันธ์อย่างแน่นหนากับที่อยู่ของการจัดสรรแทนที่จะเป็นตัวแปรตัวชี้ที่เริ่มชี้ไปที่การจัดสรรในตอนแรกการมีอาร์เรย์ย่อยสลายไปยังพอยน์เตอร์ที่ทำรหัสซึ่งประกาศว่า เป็นรหัส B ซึ่งประกาศเวกเตอร์และไม่เคยแก้ไขตัวแปรที่เก็บที่อยู่ไว้


1

ที่จริง&myarrayและmyarrayทั้งสองจะอยู่ที่ฐาน

หากคุณต้องการเห็นความแตกต่างแทนที่จะใช้

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

ใช้

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.