ทำไม x [0]! = x [0] [0]! = x [0] [0] [0]


149

ฉันกำลังศึกษา C ++ เพียงเล็กน้อยและฉันกำลังต่อสู้กับพอยน์เตอร์ ฉันเข้าใจว่าฉันสามารถมีพอยน์เตอร์ได้ 3 ระดับโดยการประกาศ:

int *(*x)[5];

เพื่อให้*xเป็นตัวชี้ไปยังอาร์เรย์ของ 5 intองค์ประกอบที่เป็นตัวชี้ไปยัง นอกจากนี้ผมรู้ว่าx[0] = *(x+0);, x[1] = *(x+1)และอื่น ๆ ....

ดังนั้นให้ประกาศข้างต้นเป็นเหตุผลว่าทำไมx[0] != x[0][0] != x[0][0][0]?


58
x[0], x[0][0]และx[0][0][0]มีชนิดที่แตกต่างกัน ไม่สามารถเปรียบเทียบได้ คุณหมายถึง!=อะไร
bolov

4
@celticminstrel พวกเขาไม่เหมือนกัน: int **x[5]เป็นอาร์เรย์ของ 5 องค์ประกอบ องค์ประกอบคือตัวชี้ไปยังตัวชี้ไปยัง
int`

5
@celticminstrel int** x[5]จะเป็นอาร์เรย์ของห้าพอยน์เตอร์ที่ชี้ไปที่พอยน์เตอร์ที่ชี้ไปที่ int int *(*x)[5]เป็นตัวชี้ไปยังอาร์เรย์ของห้าพอยน์เตอร์ที่ชี้ไปที่ int
emlai

5
@celticminstrel กฎซ้ายขวา , กฎ Spirale , C พูดพล่อยๆ↔ภาษาอังกฤษของคุณและ' ในแบบของคุณจะกลายเป็นโปรแกรมเมอร์ระดับสามดาว :)
bolov

5
@ Leo91: ประการแรกคุณมีพอยน์เตอร์สองระดับที่นี่ไม่ใช่สามระดับ ประการที่สองสิ่งที่x[0] != x[0][0] != x[0][0][0]หมายความว่าอย่างไร นี่ไม่ใช่การเปรียบเทียบที่ถูกต้องใน C ++ แม้ว่าคุณจะแยกมันออกมาx[0] != x[0][0]และx[0][0] != x[0][0][0]มันก็ยังไม่ถูกต้อง ดังนั้นคำถามของคุณหมายความว่าอย่างไร
AnT

คำตอบ:


261

xเป็นตัวชี้ไปยังอาร์เรย์ 5 พอยintน์เตอร์
x[0]คืออาร์เรย์ 5 พอยintน์เตอร์ เป็นตัวชี้ไปยัง
x[0][0] เป็น int
x[0][0][0]int

                       x[0]
   Pointer to array  +------+                                 x[0][0][0]         
x -----------------> |      |         Pointer to int           +-------+
               0x500 | 0x100| x[0][0]---------------->   0x100 |  10   |
x is a pointer to    |      |                                  +-------+
an array of 5        +------+                        
pointers to int      |      |         Pointer to int                             
               0x504 | 0x222| x[0][1]---------------->   0x222                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x508 | 0x001| x[0][2]---------------->   0x001                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x50C | 0x123| x[0][3]---------------->   0x123                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x510 | 0x000| x[0][4]---------------->   0x000                    
                     |      |                                             
                     +------+                                             

คุณจะเห็นว่า

  • x[0]เป็นอาร์เรย์และจะแปลงเป็นตัวชี้ไปยังองค์ประกอบแรกเมื่อใช้ในนิพจน์ (โดยมีข้อยกเว้นบางอย่าง) ดังนั้นx[0]จะให้ที่อยู่ขององค์ประกอบแรกของตนซึ่งเป็น x[0][0]0x500
  • x[0][0]มีที่อยู่ของซึ่งเป็น int0x100
  • x[0][0][0]มีค่าของ int10

ดังนั้นx[0]เท่ากับและดังนั้นจึง&x[0][0] ดังนั้น, .&x[0][0] != x[0][0]
x[0] != x[0][0] != x[0][0][0]


แผนภาพนี้ค่อนข้างสับสนสำหรับฉัน: 0x100ควรปรากฏขึ้นทางด้านซ้ายของช่องที่บรรจุ10อยู่ในลักษณะเดียวกับที่0x500ปรากฏทางด้านซ้ายของกล่อง แทนที่จะอยู่ไกลจากด้านซ้ายและด้านล่าง
MM

@MattMcNabb; ฉันไม่คิดว่ามันควรจะสับสน แต่การเปลี่ยนแปลงตามคำแนะนำของคุณเพื่อความชัดเจนมากขึ้น
haccks

4
@haccks - ความสุขของฉัน :) เหตุผลที่แผนภาพนั้นดีมากก็เพราะคุณไม่ต้องการคำอธิบายที่คุณให้ตามนั้น แผนภาพนั้นอธิบายตนเองได้ว่ามันตอบคำถามแล้ว ข้อความที่ตามมาเป็นเพียงโบนัส
rayryeng

1
คุณยังสามารถใช้ yed ซอฟต์แวร์สร้างไดอะแกรม มันช่วยฉันได้มากในการจัดระเบียบความคิดของฉัน
rpax

@GrijeshChauhan ผมใช้ asciiflow สำหรับความคิดเห็นรหัส ofthe, Yed สำหรับการนำเสนอ :)
rpax

133
x[0] != x[0][0] != x[0][0][0]

คือตามโพสต์ของคุณเอง

*(x+0) != *(*(x+0)+0) != *(*(*(x+0)+0)+0)`  

ซึ่งง่าย

*x != **x != ***x

ทำไมมันควรจะเท่ากัน?
อันแรกคือที่อยู่ของตัวชี้บางตัว
อันที่สองคือที่อยู่ของตัวชี้อื่น
และอันที่สามคือintค่าบางอย่าง


ฉันไม่เข้าใจ ... ถ้า x [0], x [0] [0], x [0] [0] [0] เทียบเท่ากับ * (x + 0), * (x + 0 + 0) , * (x + 0 + 0 + 0) ทำไมพวกเขาควรมีที่อยู่ที่แตกต่างกัน
Leo91

41
@ Leo91 x[0][0]เป็น(x[0])[0]คือไม่*((*(x+0))+0) *(x+0+0)dereference [0]เกิดขึ้นก่อนที่สอง
emlai

4
@ Leo91 เช่นเดียวกับx[0][0] != *(x+0+0) x[2][3] != x[3][2]
özg

@ Leo91 ความคิดเห็นที่สองที่คุณ "ได้รับทันที" ถูกลบแล้ว คุณไม่เข้าใจบางสิ่ง (ซึ่งสามารถอธิบายได้ดีกว่าในคำตอบ) หรือสิ่งนี้ไม่ใช่ของคุณหรือไม่ (บางคนชอบที่จะลบความคิดเห็นโดยไม่ต้องเนื้อหาข้อมูลมาก)
deviantfan

@deviantfan ขอโทษฉันไม่เข้าใจว่าคุณหมายถึงอะไร ฉันเข้าใจคำตอบและความคิดเห็นมากมายที่ช่วยให้ meto ชี้แจงแนวคิด
Leo91

50

นี่คือเค้าโครงหน่วยความจำของตัวชี้ของคุณ:

   +------------------+
x: | address of array |
   +------------------+
            |
            V
            +-----------+-----------+-----------+-----------+-----------+
            | pointer 0 | pointer 1 | pointer 2 | pointer 3 | pointer 4 |
            +-----------+-----------+-----------+-----------+-----------+
                  |
                  V
                  +--------------+
                  | some integer |
                  +--------------+

x[0]ผลผลิต "ที่อยู่ของอาร์เรย์",
x[0][0]ผลผลิต "ตัวชี้ 0",
x[0][0][0]"จำนวนเต็มบางส่วน"

ฉันเชื่อว่ามันควรจะชัดเจนในขณะนี้ว่าทำไมพวกเขาแตกต่างกันทั้งหมด


ข้างต้นอยู่ใกล้เพียงพอสำหรับความเข้าใจพื้นฐานซึ่งเป็นเหตุผลที่ฉันเขียนมันในแบบที่ฉันเขียน อย่างไรก็ตามเมื่อ haccks ชี้ให้เห็นอย่างถูกต้องบรรทัดแรกจะไม่แม่นยำ 100% ดังนั้นที่นี่มาทุกรายละเอียดที่ดี:

จากนิยามของภาษา C ค่าของx[0]คืออาร์เรย์ทั้งหมดของตัวชี้จำนวนเต็ม อย่างไรก็ตามอาร์เรย์เป็นสิ่งที่คุณไม่สามารถทำอะไรได้เลยใน C คุณสามารถจัดการกับที่อยู่หรือองค์ประกอบของพวกเขาได้ตลอดเวลา

  1. คุณสามารถส่งx[0]ต่อไปยังsizeofผู้ประกอบการ แต่นั่นไม่ใช่การใช้ค่าจริง ๆ ผลของมันขึ้นอยู่กับประเภทเท่านั้น

  2. คุณสามารถใช้ที่อยู่ของมันที่ทำให้ค่าของxคือ "อยู่ของอาร์เรย์" int*(*)[5]กับชนิด ในคำอื่น ๆ :&x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. ในบริบทอื่น ๆ ทั้งหมดค่าของx[0]จะสลายตัวเป็นตัวชี้ไปยังองค์ประกอบแรกในอาร์เรย์ นั่นคือตัวชี้มีค่า "ที่อยู่ของอาร์เรย์" int**และประเภทที่ เอฟเฟกต์จะเหมือนกับว่าคุณได้เลือกxใช้พint**อยน์เตอร์

เนื่องจากการสลายตัวของอาเรย์ตัวชี้ในกรณีที่ 3 การใช้งานทั้งหมดของx[0]ผลลัพธ์ในที่สุดชี้ไปที่จุดเริ่มต้นของอาเรย์ตัวชี้; การโทรprintf("%p", x[0])จะพิมพ์เนื้อหาของเซลล์หน่วยความจำที่ระบุว่าเป็น "ที่อยู่ของอาร์เรย์"


1
x[0]ไม่ใช่ที่อยู่ของอาร์เรย์
haccks

1
@haccks ใช่ตามตัวอักษรของมาตรฐานx[0]ไม่ใช่ที่อยู่ของอาเรย์มันคืออาเรย์เอง ฉันได้เพิ่มคำอธิบายในเชิงลึกของเรื่องนี้และทำไมฉันเขียนนั่นx[0]คือ "ที่อยู่ของอาร์เรย์" ฉันหวังว่าคุณจะชอบมัน.
cmaster - คืนสถานะโมนิก้า

กราฟที่ยอดเยี่ยมที่อธิบายได้อย่างสมบูรณ์แบบดี!
MK

"อย่างไรก็ตามอาร์เรย์เป็นสิ่งที่คุณไม่สามารถทำอะไรได้เลยในซี" -> ตัวอย่างตัวนับ: printf("%zu\n", sizeof x[0]);รายงานขนาดของอาร์เรย์ไม่ใช่ขนาดของตัวชี้
chux - Reinstate Monica

@ chux-ReinstateMonica และฉันก็บอกว่า "คุณมักจะจัดการกับที่อยู่หรือองค์ประกอบของพวกเขาไม่เคยอาร์เรย์ทั้งหมดในภาพรวม" ตามด้วยจุดที่ 1 ของการแจงนับที่ฉันพูดถึงผลกระทบของsizeof x[0]...
cmaster - คืนสถานะโมนิก้า

18
  • x[0]dereferences ตัวชี้นอกสุด ( ตัวชี้ไปยังอาร์เรย์ขนาด 5 ของตัวชี้ไปยัง int) และผลลัพธ์ในอาร์เรย์ขนาด 5 ของตัวชี้ไปยังint;
  • x[0][0]dereferences ตัวชี้นอกสุดและทำดัชนีอาร์เรย์ส่งผลให้ตัวชี้ไปที่int;
  • x[0][0][0] ทุกอย่าง dereferences ส่งผลให้มูลค่าที่เป็นรูปธรรม

โดยวิธีการที่ถ้าคุณเคยรู้สึกสับสนโดยสิ่งที่ชนิดของการประกาศเหล่านี้หมายถึงการใช้cdecl


11

อนุญาตพิจารณาขั้นตอนโดยขั้นตอนการแสดงออกx[0], และx[0][0]x[0][0][0]

ตามที่xกำหนดไว้ด้วยวิธีการดังต่อไปนี้

int *(*x)[5];

ดังนั้น expression x[0]จึงเป็นประเภทอาเรint *[5]ย์ คำนึงถึงว่าการแสดงออกจะเทียบเท่ากับการแสดงออกx[0] *xนั่นคือการลดทอนตัวชี้ไปยังอาร์เรย์ที่เราได้รับอาร์เรย์ อนุญาตแสดงว่ามันเหมือน y นั่นคือเรามีคำประกาศ

int * y[5];

การแสดงออกx[0][0]เทียบเท่ากับและมีประเภทy[0] int *อนุญาตแสดงว่ามันเหมือน z นั่นคือเรามีคำประกาศ

int *z;

การแสดงออกx[0][0][0]จะเทียบเท่ากับการแสดงออกy[0][0]ที่ในการเปิดจะเทียบเท่ากับการแสดงออกและมีประเภทz[0]int

ดังนั้นเราจึงมี

x[0] มีประเภท int *[5]

x[0][0] มีประเภท int *

x[0][0][0] มีประเภท int

ดังนั้นมันจึงเป็นวัตถุประเภทต่าง ๆ และด้วยขนาดที่แตกต่างกัน

เรียกใช้ตัวอย่างเช่น

std::cout << sizeof( x[0] ) << std::endl;
std::cout << sizeof( x[0][0] ) << std::endl;
std::cout << sizeof( x[0][0][0] ) << std::endl;

10

สิ่งแรกที่ฉันต้องบอกว่า

x [0] = * (x + 0) = * x;

x [0] [0] = * (* (x + 0) + 0) = * * x;

x [0] [0] [0] = * (* (* (x + 0) + 0)) = * * * x;

ดังนั้น * x ≠ * * x ≠ * * * x

จากภาพต่อไปนี้ทุกสิ่งชัดเจน

  x[0][0][0]= 2000

  x[0][0]   = 1001

  x[0]      = 10

ป้อนคำอธิบายรูปภาพที่นี่

มันเป็นเพียงตัวอย่างโดยที่ค่าของx [0] [0] [0] = 10

และที่อยู่ของx [0] [0] [0]คือ1001

ที่อยู่นั้นจะถูกเก็บไว้ในx [0] [0] = 1001

และที่อยู่ของx [0] [0]คือ2000

และที่อยู่นั้นจะถูกเก็บไว้ที่x [0] = 2000

ดังนั้นx [0] [0] [0] x [0] [0] x [0]

.

editings

โปรแกรม 1:

{
int ***x;
x=(int***)malloc(sizeof(int***));
*x=(int**)malloc(sizeof(int**));
**x=(int*)malloc(sizeof(int*));
***x=10;
printf("%d   %d   %d   %d\n",x,*x,**x,***x);
printf("%d   %d   %d   %d   %d",x[0][0][0],x[0][0],x[0],x,&x);
}

เอาท์พุต

142041096 142041112 142041128 10
10 142041128 142041112 142041096 -1076392836

โปรแกรม 2:

{
int x[1][1][1]={10};
printf("%d   %d   %d   %d \n ",x[0][0][0],x[0][0],x[0],&x);
}

เอาท์พุต

10   -1074058436   -1074058436   -1074058436 

3
คำตอบของคุณทำให้เข้าใจผิด x[0]ไม่มีที่อยู่มด มันเป็นอาร์เรย์ มันจะสลายตัวเพื่อชี้ไปที่องค์ประกอบแรก
haccks

อืม ... มันหมายถึงอะไร? การแก้ไขของคุณเป็นเหมือนเชอร์รี่เค้กเพื่อคำตอบที่ผิดของคุณ มันไม่มีเหตุผล
haccks

@haccks หากใช้พอยน์เตอร์เพียงอย่างเดียวคำตอบนี้จะถูกต้อง จะมีการเปลี่ยนแปลงบางส่วนในที่อยู่เมื่อใช้ Array
apm

7

หากคุณต้องดูอาร์เรย์จากมุมมองของโลกแห่งความเป็นจริงมันจะปรากฏดังนี้:

x[0]เป็นคอนเทนเนอร์ขนส่งสินค้าที่เต็มไปด้วยลังไม้
x[0][0]เป็นลังเดียวเต็มไปด้วยกล่องรองเท้าภายในตู้สินค้า
x[0][0][0]เป็น shoebox เดียวภายในลังภายในคอนเทนเนอร์ขนส่ง

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


1
จะไม่x[0][0]เป็นลังเดี่ยวที่เต็มไปด้วยกระดาษที่มีตำแหน่งของกล่องรองเท้าเขียนอยู่หรือเปล่า
wchargin

4

มีหลักการใน C ++ ดังนี้: การประกาศตัวแปรระบุวิธีการใช้ตัวแปรอย่างแม่นยำ พิจารณาคำประกาศของคุณ:

int *(*x)[5];

ที่สามารถเขียนใหม่เป็น (สำหรับชัดเจน):

int *((*x)[5]);

เนื่องจากหลักการเรามี:

*((*x)[i]) is treated as an int value (i = 0..4)
 (*x)[i] is treated as an int* pointer (i = 0..4)
 *x is treated as an int** pointer
 x is treated as an int*** pointer

ดังนั้น:

x[0] is an int** pointer
 x[0][0] = (x[0]) [0] is an int* pointer
 x[0][0][0] = (x[0][0]) [0] is an int value

ดังนั้นคุณสามารถหาความแตกต่าง


1
x[0]เป็นอาร์เรย์จำนวน 5 int ไม่ใช่ตัวชี้ (อาจสลายไปถึงตัวชี้ในบริบทส่วนใหญ่ แต่ความแตกต่างมีความสำคัญที่นี่)
MM

โอเค แต่คุณควรพูดว่า: x [0] เป็นอาร์เรย์ของ 5 พอยน์เตอร์ int *
Nghia Bui

เพื่อให้ผู้ที่มาที่ถูกต้องต่อ @MattMcNabb: *(*x)[5]เป็นintเพื่อให้(*x)[5]เป็นint *เพื่อให้*xเป็น(int *)[5]เพื่อให้เป็นx *((int *)[5])นั่นคือxเป็นตัวชี้ไปยัง 5 พอยน์เตอร์ของพอยintน์เตอร์
wchargin

2

คุณกำลังพยายามเปรียบเทียบประเภทต่าง ๆ ตามค่า

หากคุณใช้ที่อยู่คุณอาจได้รับมากกว่าที่คุณคาดหวัง

โปรดทราบว่าการประกาศของคุณสร้างความแตกต่าง

 int y [5][5][5];

จะช่วยให้รถที่คุณต้องการตั้งแต่y, y[0], y[0][0], y[0][0][0]จะมีค่าที่แตกต่างกันและประเภท แต่ที่อยู่เดียวกัน

int **x[5];

ไม่ใช้พื้นที่ต่อเนื่อง

xและx [0]มีที่อยู่เดียวกัน แต่x[0][0]และx[0][0][0]แต่ละที่อยู่แตกต่างกัน


2
int *(*x)[5]แตกต่างกับint **x[5]
MM

2

เป็นpตัวชี้: คุณกำลังซ้อน dereferences ด้วยซึ่งเทียบเท่ากับp[0][0]*((*(p+0))+0)

ในเครื่องหมาย C อ้างอิง (&) และเครื่องหมาย dereference (*):

p == &p[0] == &(&p[0])[0] == &(&(&p[0])[0])[0])

เทียบเท่ากับ:

p == &*(p+0) == &*(&*(p+0))+0 == &*(&*(&*(p+0))+0)+0

ดูว่า & re * สามารถ refactored เพียงแค่ลบมัน:

p == p+0 == p+0+0 == p+0+0+0 == (((((p+0)+0)+0)+0)+0)

คุณพยายามแสดงอะไรกับทุกสิ่งหลังจากประโยคแรก คุณเพียงแค่มีรูปแบบp == p มากมาย &(&p[0])[0]ต่างกับp[0][0]
MM

ชายคนนั้นถามว่าทำไม 'x [0]! = x [0] [0]! = x [0] [0] [0]' เมื่อ x เป็นตัวชี้ใช่ไหม? ฉันพยายามที่จะแสดงให้เขาเห็นว่าเขาสามารถติดกับสัญลักษณ์ C ของ dereference (*) เมื่อเขาสแต็คของ [0] ดังนั้นมันจึงเป็นความพยายามที่จะแสดงสัญกรณ์ที่ถูกต้องให้เขามี x เท่ากับ x [0] โดยอ้างอิง x [0] อีกครั้งด้วย & และอื่น ๆ
Luciano

1

คำตอบอื่น ๆ นั้นถูกต้อง แต่ไม่มีคำตอบใดที่เน้นความคิดว่าเป็นไปได้ที่ทั้งสามคนจะมีค่าเท่ากันดังนั้นจึงไม่สมบูรณ์

เหตุผลที่ไม่สามารถเข้าใจได้จากคำตอบอื่น ๆ คือภาพประกอบทั้งหมดในขณะที่มีประโยชน์และสมเหตุสมผลในสถานการณ์ส่วนใหญ่ไม่สามารถครอบคลุมสถานการณ์ที่ตัวชี้ชี้xไปที่ตัวเอง

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

หมายเหตุ:ลักษณะการทำงานในโปรแกรมนี้จะไม่ได้กำหนด แต่ฉันโพสต์ได้ที่นี่อย่างหมดจดเป็นการสาธิตที่น่าสนใจของสิ่งที่ตัวชี้สามารถทำ แต่ไม่ควร

#include <stdio.h>

int main () {
  int *(*x)[5];

  x = (int *(*)[5]) &x;

  printf("%p\n", x[0]);
  printf("%p\n", x[0][0]);
  printf("%p\n", x[0][0][0]);
}

สิ่งนี้รวบรวมโดยไม่มีคำเตือนทั้งใน C89 และ C99 และผลลัพธ์มีดังต่อไปนี้:

$ ./ptrs
0xbfd9198c
0xbfd9198c
0xbfd9198c

น่าสนใจพอค่าทั้งสามนั้นเหมือนกัน แต่นี่ไม่น่าแปลกใจ! ก่อนอื่นเรามาแบ่งโปรแกรม

เราประกาศxเป็นตัวชี้ไปยังอาร์เรย์ของ 5 องค์ประกอบที่แต่ละองค์ประกอบเป็นประเภทของตัวชี้ไปยัง int การประกาศนี้จัดสรร 4 ไบต์บนรันไทม์สแต็ก (หรือมากกว่านั้นขึ้นอยู่กับการใช้งานของคุณบนเครื่องพอยน์เตอร์ของฉันคือ 4 ไบต์) ดังนั้นxอ้างถึงตำแหน่งหน่วยความจำจริง ในตระกูลภาษา C เนื้อหาของxเป็นเพียงขยะบางสิ่งที่หลงเหลือจากการใช้สถานที่ก่อนหน้านี้ดังนั้นxตัวมันเองจึงไม่ได้ชี้ไปที่ใดเลย - แน่นอนว่าจะไม่จัดสรรพื้นที่

ดังนั้นโดยธรรมชาติเราสามารถหาที่อยู่ของตัวแปรxและวางไว้ที่ไหนสักแห่งนั่นคือสิ่งที่เราทำ แต่เราจะไปข้างหน้าและใส่มันเข้าไปในตัว x เนื่องจาก&xมีประเภทที่แตกต่างจากxเราจึงต้องทำการแสดงดังนั้นเราจึงไม่ได้รับคำเตือน

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

0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+

ดังนั้นบล็อก 4 ไบต์ของหน่วยความจำที่อยู่ที่มีรูปแบบบิตที่สอดคล้องกับค่าฐานสิบหก0xbfd9198c 0xbfd9198cเรียบง่ายพอสมควร

ต่อไปเราจะพิมพ์ค่าสามค่า คำตอบอื่น ๆ จะอธิบายความหมายของแต่ละนิพจน์ดังนั้นความสัมพันธ์ควรจะชัดเจนในขณะนี้

เราสามารถเห็นได้ว่าค่าเหมือนกัน แต่ในระดับต่ำมากเท่านั้น ... รูปแบบบิตของพวกเขาเหมือนกัน แต่ข้อมูลประเภทที่เกี่ยวข้องกับแต่ละนิพจน์หมายความว่าค่าที่ตีความนั้นแตกต่างกัน ตัวอย่างเช่นหากเราพิมพ์x[0][0][0]โดยใช้สตริงรูปแบบ%dเราจะได้จำนวนลบมากดังนั้น "ค่า" จึงแตกต่างกัน แต่ในทางปฏิบัติรูปแบบบิตนั้นเหมือนกัน

นี่เป็นเรื่องง่ายจริงๆ ... ในไดอะแกรมลูกศรเพิ่งชี้ไปยังที่อยู่หน่วยความจำเดียวกันมากกว่าที่จะอยู่ต่างหน่วย อย่างไรก็ตามในขณะที่เราสามารถบังคับให้ผลลัพธ์ที่คาดหวังจากพฤติกรรมที่ไม่ได้กำหนดเป็นเพียงแค่นั้น - ไม่ได้กำหนด นี่ไม่ใช่รหัสการผลิต แต่เป็นการสาธิตเพื่อความสมบูรณ์

ในสถานการณ์ที่สมเหตุสมผลคุณจะใช้mallocเพื่อสร้างอาร์เรย์ของ 5 พอยน์เตอร์พอยน์เตอร์และอีกครั้งเพื่อสร้าง ints ที่ชี้ไปในอาเรย์นั้น mallocส่งกลับที่อยู่ที่ไม่ซ้ำกันเสมอ (เว้นแต่ว่าคุณมีหน่วยความจำไม่เพียงพอซึ่งในกรณีนี้จะส่งคืนค่า NULL หรือ 0) ดังนั้นคุณจะไม่ต้องกังวลเกี่ยวกับตัวชี้การอ้างอิงตนเองเช่นนี้

หวังว่านั่นเป็นคำตอบที่คุณต้องการ คุณไม่ควรคาดหวังx[0], x[0][0]และx[0][0][0]จะเป็นเท่ากัน แต่พวกเขาอาจจะถ้าบังคับ หากมีอะไรเกิดขึ้นในหัวของคุณโปรดแจ้งให้เราทราบเพื่อที่ฉันจะได้อธิบายให้ชัดเจน!


ฉันจะบอกว่าการใช้พอยน์เตอร์แปลก ๆ ที่ฉันเคยเห็นอีกครั้ง
haccks

@haccks ใช่มันค่อนข้างแปลก แต่เมื่อคุณทำลายมันมันก็เป็นพื้นฐานเช่นเดียวกับตัวอย่างอื่น ๆ มันเกิดขึ้นเป็นกรณีที่รูปแบบบิตเหมือนกันหมด
Purag

รหัสของคุณทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด x[0]ไม่ได้แสดงถึงวัตถุที่ถูกต้องของประเภทที่ถูกต้อง
MM

@ MattMcNabb มันไม่ได้กำหนดและฉันชัดเจนมากเกี่ยวกับเรื่องนั้นจริง ฉันไม่เห็นด้วยกับประเภท xเป็นตัวชี้ไปยังอาร์เรย์ดังนั้นเราจึงสามารถใช้[]โอเปอเรเตอร์เพื่อระบุการชดเชยจากตัวชี้นั้นและทำการอ้างอิงอีกครั้ง มีอะไรแปลก ๆ ผลลัพธ์ของx[0]การเป็นอาร์เรย์และ C ไม่บ่นถ้าคุณพิมพ์ที่ใช้%pเนื่องจากเป็นวิธีการใช้งานอยู่ด้านล่าง
Purag

และรวบรวมนี้ด้วย-pedanticธงผลิตไม่มีคำเตือนเพื่อให้ซีเป็นดีกับชนิด ...
Purag

0

ชนิดของint *(*x)[5]is int* (*)[5]คือตัวชี้ไปยังอาร์เรย์ของ 5 พอยน์เตอร์ถึง ints

  • xคือที่อยู่ของอาร์เรย์แรกของ 5 พอยน์เตอร์ถึง ints (ที่อยู่ที่มีประเภทint* (*)[5])
  • x[0]ที่อยู่ของอาเรย์แรกของ 5 พอยน์เตอร์ไปยัง ints (ที่อยู่เดียวกันกับประเภทint* [5]) (ออฟเซ็ตที่อยู่ x ด้วย0*sizeof(int* [5])เช่นดัชนี * ขนาดของประเภทกำลังชี้ไปที่และอ้างอิง)
  • x[0][0]เป็นตัวชี้แรกไปยัง int ในอาร์เรย์ (ที่อยู่เดียวกันกับประเภทint*) (ที่อยู่ตรงข้าม x โดย0*sizeof(int* [5])และ dereference แล้วโดย0*sizeof(int*)และ dereference)
  • x[0][0][0]เป็น int แรกที่ชี้ไปโดยตัวชี้ไปยัง int (offset address x by 0*sizeof(int* [5])และ dereference และ offset ที่อยู่โดย0*sizeof(int*)และ dereference และ offset ที่อยู่โดย0*sizeof(int)และ dereference)

ประเภทของint *(*y)[5][5][5]คือint* (*)[5][5][5]ตัวชี้ไปยังอาร์เรย์ 3 มิติของตัวชี้ 5x5x5 เพื่อ ints

  • x เป็นที่อยู่ของอาร์เรย์ 3 มิติแรกของตัวชี้ 5x5x5 เพื่อ ints พร้อมกับประเภท int*(*)[5][5][5]
  • x[0]เป็นที่อยู่ของอาร์เรย์ 3 มิติแรกของตัวชี้ 5x5x5 ไปยัง ints (ชดเชยที่อยู่ x ด้วย0*sizeof(int* [5][5][5])และการลงทะเบียน)
  • x[0][0]เป็นที่อยู่ของอาร์เรย์ 2d แรกของตัวชี้ 5x5 ถึง ints (offset address x by 0*sizeof(int* [5][5][5])และ dereference จากนั้นชดเชยที่อยู่ด้วย0*sizeof(int* [5][5]))
  • x[0][0][0]เป็นที่อยู่ของอาเรย์แรกของ 5 พอยน์เตอร์ให้เป็น ints (ออฟเซ็ตที่อยู่ x คูณด้วย0*sizeof(int* [5][5][5])และ dereference และออฟเซ็ตนั้นโดย0*sizeof(int* [5][5])และหักล้างที่อยู่ด้วย0*sizeof(int* [5]))
  • x[0][0][0][0]เป็นตัวชี้แรกไปยัง int ในอาร์เรย์ (offset address x by 0*sizeof(int* [5][5][5])และ dereference และ offset ที่อยู่โดย0*sizeof(int* [5][5])และ offset address นั้น0*sizeof(int* [5])และชดเชยที่อยู่ด้วย0*sizeof(int*)และ dereference)
  • x[0][0][0][0][0]เป็น int แรกที่ชี้ไปยังตัวชี้ไปยัง int (offset address x by 0*sizeof(int* [5][5][5])และ dereference และ offset ที่อยู่โดย0*sizeof(int* [5][5])และ offset ที่อยู่โดย0*sizeof(int* [5])และ offset ที่อยู่ด้วย0*sizeof(int*)และชดเชยที่อยู่โดยและ dereference และชดเชยที่อยู่โดย0*sizeof(int)และ dereference)

สำหรับการสลายตัวของอาร์เรย์:

void function (int* x[5][5][5]){
  printf("%p",&x[0][0][0][0]); //get the address of the first int pointed to by the 3d array
}

สิ่งนี้เทียบเท่ากับการผ่านint* x[][5][5]หรือint* (*x)[5][5]กล่าวคือพวกมันทั้งหมดสลายตัวไปหลัง นี่คือเหตุผลที่คุณจะไม่ได้รับคำเตือนของคอมไพเลอร์สำหรับใช้x[6][0][0]ในฟังก์ชั่น แต่คุณจะทำx[0][6][0]เพราะข้อมูลขนาดนั้นจะถูกเก็บไว้

void function (int* (*x)[5][5][5]){
  printf("%p",&x[0][0][0][0][0]); //get the address of the first int pointed to by the 3d array
}
  • x[0] เป็นที่อยู่ของอาร์เรย์ 3 มิติแรกของตัวชี้ 5x5x5 ไปยัง ints
  • x[0][0] เป็นที่อยู่ของอาร์เรย์ 2d แรกของตัวชี้ 5x5 ถึง ints
  • x[0][0][0] เป็นที่อยู่ของอาร์เรย์แรกของ 5 พอยน์เตอร์ถึง ints
  • x[0][0][0][0] เป็นตัวชี้แรกไปยัง int ในอาร์เรย์
  • x[0][0][0][0][0] เป็น int แรกที่ถูกชี้โดยตัวชี้ไปยัง int

ในตัวอย่างสุดท้ายมันชัดเจนกว่าที่จะใช้*(*x)[0][0][0]มากกว่าความหมายx[0][0][0][0][0]นี่เป็นเพราะครั้งแรกและครั้งสุดท้าย[0]ที่นี่ถูกตีความว่าเป็นตัวชี้ถึงความไม่ลงรอยกันมากกว่าดัชนีเข้าสู่อาเรย์หลายมิติเพราะประเภท อย่างไรก็ตามมันเหมือนกันเพราะ(*x) == x[0]ไม่คำนึงถึงความหมาย คุณสามารถใช้*****xซึ่งจะดูเหมือนว่าคุณกำลังลงทะเบียนตัวชี้ 5 ครั้ง แต่จริง ๆ แล้วมันถูกตีความเหมือนกัน: ออฟเซต, dereference, dereference, 2 offsets ในอาร์เรย์และ dereference ล้วนๆเพราะประเภท คุณกำลังใช้การดำเนินการกับ

โดยเฉพาะอย่างยิ่งเมื่อคุณ[0]หรือเป็นชนิดอาร์เรย์ไม่ใช่มันเป็น offset และ dereference เนื่องจากการลำดับความสำคัญของ***(a + 0)

เมื่อคุณ[0]หรือ*a *ไปยังประเภทอาเรย์นั้นจะเป็นการออฟเซตจากนั้นการอ่านค่าแบบ idempotent (คอมไพเลอร์ได้รับการแก้ไขโดยคอมไพเลอร์เพื่อให้ได้แอดเดรสเดียวกัน - เป็นการดำเนินการแบบ idempotent)

เมื่อคุณ[0]หรือ*ประเภทที่มีประเภทอาร์เรย์ 1d มันจะเป็นการชดเชย

หากคุณ[0]หรือ**ประเภทอาเรย์ 2d แสดงว่ามันเป็นอ็อฟเซ็ตเท่านั้นนั่นคือออฟเซ็ตและจากนั้นจึงเป็นการค้นหาแบบ idempotent

หากคุณ[0][0][0]หรือ***ประเภทอาเรย์ 3 มิติก็จะเป็นการชดเชยการชดเชย + idempotent จากนั้นการชดเชยการชดเชย + idempotent แล้วการชดเชยการชดเชย + idempotent แล้วการอ้างอิง การอ้างอิงที่แท้จริงจะเกิดขึ้นก็ต่อเมื่อประเภทอาเรย์ถูกถอดออกทั้งหมดเท่านั้น

สำหรับตัวอย่างของint* (*x)[1][2][3]ประเภทที่ยังไม่ได้เปิดในลำดับ

  • x มีประเภท int* (*)[1][2][3]
  • *xมีประเภทint* [1][2][3](ชดเชย 0 + การค้นหาที่ผิดปกติของ idempotent)
  • **xมีประเภทint* [2][3](ชดเชย 0 + การค้นหาที่ผิดปกติของ idempotent)
  • ***xมีประเภทint* [3](ชดเชย 0 + การค้นหาที่ผิดปกติของ idempotent)
  • ****xมีประเภทint*(ชดเชย 0 + การอ้างอิง)
  • *****xมีประเภทint(ชดเชย 0 + การอ้างอิง)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.