ตัวชี้ C ไปยังอาเรย์ / อาเรย์ของตัวชี้ความเข้าใจผิด


463

ความแตกต่างระหว่างการประกาศต่อไปนี้คืออะไร:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

กฎทั่วไปในการทำความเข้าใจการประกาศที่ซับซ้อนมากขึ้นคืออะไร?


54
นี่คือบทความดี ๆ เกี่ยวกับการอ่านประกาศที่ซับซ้อนใน C: unixwiz.net/techtips/reading-cdecl.html
Jesper

@jesper แต่น่าเสียดายที่constและผู้volatileคัดเลือกซึ่งมีทั้งความสำคัญและยุ่งยากนั้นหายไปในบทความนั้น
ไม่ใช่ผู้ใช้

@ ไม่ใช่ผู้ใช้ที่ไม่เกี่ยวข้องกับคำถามนี้ ความคิดเห็นของคุณไม่เกี่ยวข้อง โปรดงดเว้น
user64742

คำตอบ:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

อันที่สามเหมือนกันกับอันแรก

กฎทั่วไปเป็นสำคัญประกอบ มันสามารถซับซ้อนมากยิ่งขึ้นเมื่อตัวชี้ฟังก์ชั่นเข้ามาในรูปภาพ


4
ดังนั้นสำหรับระบบ 32 บิต: int * arr [8]; / * 8x4 ไบต์จัดสรรสำหรับตัวชี้แต่ละตัว/ int (* arr) [8]; / 4 ไบต์จัดสรรเพียงตัวชี้ * /
George

10
Nope int * arr [8]: 8x4 ไบต์จัดสรรรวมทั้งหมด 4 ไบต์สำหรับตัวชี้แต่ละตัว int (* arr) [8] ถูกต้อง 4 ไบต์
Mehrdad Afshari

2
ฉันควรอ่านสิ่งที่ฉันเขียนใหม่ ฉันหมายถึง 4 สำหรับตัวชี้แต่ละตัว ขอบคุณสำหรับความช่วยเหลือ!
จอร์จ

4
เหตุผลที่คนแรกเหมือนกันกับคนสุดท้ายคือมันได้รับอนุญาตเสมอในการห่อวงเล็บรอบผู้ประกาศ P [N] เป็นตัวประกาศอาร์เรย์ P (.... ) เป็นตัวประกาศฟังก์ชันและ * P เป็นตัวประกาศตัวชี้ ดังนั้นทุกอย่างในต่อไปนี้จะเหมือนกับไม่มีวงเล็บ (ยกเว้นหนึ่งในฟังก์ชัน '"()": int ((* * p))); void ((g (void))); int * (a [1]); void (* (p ())).
Johannes Schaub - litb

2
ทำได้ดีในคำอธิบายของคุณ สำหรับการอ้างอิงในเชิงลึกเกี่ยวกับความสำคัญและความสัมพันธ์ของตัวดำเนินการอ้างอิงที่หน้า 53 ของภาษาการเขียนโปรแกรม C (ANSI C รุ่นที่สอง) โดย Brian Kernighan และ Dennis Ritchie ตัวดำเนินการ( ) [ ] เชื่อมโยงจากซ้ายไปขวาและมีลำดับความสำคัญสูงกว่า*ดังนั้นอ่านint* arr[8]เป็นอาร์เรย์ขนาด 8 ซึ่งแต่ละองค์ประกอบชี้ไปที่ int และint (*arr)[8]เป็นตัวชี้ไปยังอาร์เรย์ขนาด 8 ที่เก็บจำนวนเต็ม
Mushy

267

ใช้โปรแกรมcdeclตามที่ K&R แนะนำ

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

มันทำงานในทางอื่นด้วย

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankii ลีนุกซ์ส่วนใหญ่ควรมีแพ็คเกจ. คุณสามารถสร้างไบนารีของคุณเอง
sigjuice

อาขอโทษที่ไม่ได้กล่าวถึง macOS ที่นี่ จะดูว่ามีหรือไม่เช่นนั้นเว็บไซต์ก็ใช้ได้เช่นกัน ^^ ขอบคุณที่แจ้งให้เราทราบเกี่ยวกับเรื่องนี้ .. อย่าลังเลที่จะตั้งค่าสถานะ NLN
ankii

2
@ankii คุณสามารถติดตั้งจาก Homebrew (และอาจจะเป็น MacPort?) หากสิ่งเหล่านั้นไม่ใช่รสนิยมของคุณมันเป็นเรื่องเล็กน้อยที่จะสร้างลิงก์ของคุณเองขึ้นมาจากลิงค์ Github ที่ด้านบนขวาของ cdecl.org (ฉันเพิ่งสร้างมันบน macOS Mojave) จากนั้นเพียงคัดลอกไบนารี cdecl ไปยัง PATH ของคุณ ฉันขอแนะนำ $ PATH / bin เพราะไม่จำเป็นต้องมีส่วนร่วมในสิ่งที่ง่ายเหมือนนี้
sigjuice

โอ้ไม่ได้อ่านย่อหน้าเล็กน้อยเกี่ยวกับการติดตั้งใน readme เพียงแค่คำสั่งและแฟล็กสำหรับการจัดการการพึ่งพา .. ติดตั้งโดยใช้การชง :)
ankii

1
เมื่อฉันอ่านสิ่งนี้ครั้งแรกฉันคิดว่า: "ฉันจะไม่ลงมาถึงระดับนี้" ในวันถัดไปฉันดาวน์โหลด
Ciro Santilli 郝海东冠状病六四事件法轮功

126

ฉันไม่รู้ว่ามันมีชื่ออย่างเป็นทางการหรือไม่ แต่ฉันเรียกมันว่า Right-Left Thingy (TM)

เริ่มต้นที่ตัวแปรจากนั้นไปทางขวาและซ้ายและขวา ... เป็นต้น

int* arr1[8];

arr1 คืออาร์เรย์ 8 พอยน์เตอร์ถึงจำนวนเต็ม

int (*arr2)[8];

arr2 เป็นตัวชี้ (วงเล็บปิดกั้นทางซ้ายขวา) ไปยังอาร์เรย์จำนวนเต็ม 8 ตัว

int *(arr3[8]);

arr3 คืออาร์เรย์ 8 พอยน์เตอร์ถึงจำนวนเต็ม

สิ่งนี้จะช่วยคุณออกประกาศที่ซับซ้อน


19
ผมเคยได้ยินมันเรียกตามชื่อของ "เวียนกฎ" ซึ่งสามารถพบได้ที่นี่
fouric

6
@InkBlend: กฎเกลียวจะแตกต่างจากกฎซ้ายขวา อดีตล้มเหลวในกรณีเช่นint *a[][10]ในขณะที่หลังประสบความสำเร็จ
ตำนาน 2k

1
@dogeen ฉันคิดว่าคำนั้นเกี่ยวข้องกับ Bjarne Stroustrup :)
Anirudh Ramanathan

1
ดังที่ InkBlend และ legends2k กล่าวว่านี่เป็น Spiral Rule ซึ่งซับซ้อนกว่าและไม่สามารถใช้งานได้ในทุกกรณีดังนั้นจึงไม่มีเหตุผลที่จะใช้
kotlomoy

อย่าลืมระดับการเชื่อมโยงทางด้านขวาของ( ) [ ]และทางด้านซ้ายของ* &
Mushy

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

ไม่ควรเป็นอันที่ 3: a คือพอยน์เตอร์ของพอยน์เตอร์ไปยังอาร์เรย์จำนวนเต็มขนาด 8 หรือไม่? ฉันหมายถึงแต่ละจำนวนเต็มอาร์เรย์จะมีขนาด 8 ใช่มั้ย
Rushil Paul

2
@Rushil: ไม่ตัวห้อยสุดท้าย ( [5]) แสดงถึงมิติด้านใน นี่หมายความว่า(*a[8])เป็นมิติแรกและดังนั้นจึงเป็นตัวแทนด้านนอกของอาร์เรย์ สิ่งที่แต่ละองค์ประกอบภายในa คะแนนเป็นอาร์เรย์จำนวนเต็มขนาดแตกต่างกัน 5
zeboidlund

ขอบคุณสำหรับบุคคลที่สาม ฉันกำลังหาวิธีเขียนอาร์เรย์ของพอยน์เตอร์ไปยังอาร์เรย์
Deqing

15

คำตอบสำหรับสองคนสุดท้ายนั้นสามารถหักออกจากกฎทองใน C:

การประกาศตามการใช้งาน

int (*arr2)[8];

จะเกิดอะไรขึ้นถ้าคุณ dereference arr2? คุณได้อาร์เรย์จำนวนเต็ม 8 จำนวน

int *(arr3[8]);

จะเกิดอะไรขึ้นถ้าคุณนำองค์ประกอบมาจากarr3อะไร คุณได้รับตัวชี้ไปยังจำนวนเต็ม

นอกจากนี้ยังช่วยเมื่อจัดการกับตัวชี้ไปยังฟังก์ชั่น ในการใช้ตัวอย่างของ sigjuice:

float *(*x)(void )

จะเกิดอะไรขึ้นเมื่อคุณ dereference x? คุณได้รับฟังก์ชั่นที่คุณสามารถเรียกได้โดยไม่มีข้อโต้แย้ง จะเกิดอะไรขึ้นเมื่อคุณเรียกมันว่า floatมันจะกลับตัวชี้ไปยัง

แม้ว่าความสำคัญของผู้ประกอบการจะยุ่งยากเสมอ อย่างไรก็ตามการใช้วงเล็บอาจทำให้เกิดความสับสนได้เนื่องจากการประกาศตามมาใช้ อย่างน้อยสำหรับฉันอย่างสังหรณ์ใจarr2ดูเหมือนอาเรย์ 8 พอยน์เตอร์ที่จะ ints แต่จริง ๆ แล้วมันเป็นวิธีอื่น ๆ ใช้เวลาแค่บางคนเริ่มชินแล้ว มีเหตุผลมากพอที่จะเพิ่มความคิดเห็นในการประกาศเหล่านี้เสมอหากคุณถามฉัน :)

แก้ไข: ตัวอย่าง

โดยวิธีการที่ฉันเพิ่งสะดุดในสถานการณ์ต่อไปนี้: ฟังก์ชั่นที่มีเมทริกซ์แบบคงที่และที่ใช้เลขคณิตของตัวชี้เพื่อดูว่าตัวชี้แถวที่อยู่นอกขอบเขต ตัวอย่าง:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

เอาท์พุท:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

โปรดทราบว่าค่าของเส้นขอบจะไม่เปลี่ยนแปลงดังนั้นคอมไพเลอร์สามารถปรับค่าให้เหมาะสม สิ่งนี้แตกต่างจากสิ่งที่คุณอาจต้องการใช้ในตอนแรกconst int (*border)[3]:: ที่ประกาศเส้นขอบเป็นตัวชี้ไปยังอาร์เรย์จำนวนเต็ม 3 จำนวนที่จะไม่เปลี่ยนค่าตราบใดที่ตัวแปรนั้นมีอยู่ อย่างไรก็ตามตัวชี้นั้นอาจชี้ไปยังอาเรย์อื่น ๆ ได้ตลอดเวลา เราต้องการพฤติกรรมแบบนั้นสำหรับการโต้แย้งแทน (เพราะฟังก์ชั่นนี้ไม่ได้เปลี่ยนจำนวนเต็มใด ๆ ) การประกาศตามการใช้งาน

(ps: อย่าลังเลที่จะปรับปรุงตัวอย่างนี้!)



3

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


นั่นเป็นความจริง แต่มันก็เป็น ilegal ด้วย คุณไม่สามารถมีฟังก์ชั่นที่ส่งกลับอาร์เรย์ ฉันลองและได้รับสิ่งนี้: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];ภายใต้ GCC 8 ด้วย$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito

1
เหตุผลที่คอมไพเลอร์ให้ข้อผิดพลาดนั้นคือมันเป็นการตีความฟังก์ชันเป็นการส่งคืนอาร์เรย์เนื่องจากเป็นการตีความที่ถูกต้องของสถานะกฎที่มีความสำคัญ มันเป็น ilegal เป็นการประกาศ แต่การประกาศทางกฎหมายint *(*ptr)();อนุญาตให้ใช้การแสดงออกเช่นp()[3](หรือ(*p)()[3]) ในภายหลัง
Luis Colorado เมื่อ

ตกลงถ้าฉันเข้าใจคุณกำลังพูดถึงการสร้างฟังก์ชั่นที่ส่งกลับตัวชี้ไปยังองค์ประกอบแรกของอาเรย์ (ไม่ใช่อาเรย์ตัวเอง) แล้วใช้ฟังก์ชั่นนั้นในภายหลังราวกับว่ามันส่งคืนอาเรย์? ความคิดที่น่าสนใจ ฉันจะลองดู int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }และเรียกมันว่าสิ่งนี้foo(arr)[4];ซึ่งควรมีarr[2][4]ใช่ไหม?
Cacahuete Frito

ใช่ ... แต่คุณก็พูดถูก :)
Luis Colorado

2

ฉันคิดว่าเราสามารถใช้กฎง่าย ๆ ได้ ..

example int * (*ptr)()[];
start from ptr 

" ptrคือตัวชี้" ไปทางขวา .. ")" ตอนนี้ไปทางซ้าย "(" ออกมาด้านขวา "()" ดังนั้น "ไปยังฟังก์ชันที่ไม่มีอาร์กิวเมนต์" ไปทางซ้าย "และส่งกลับตัวชี้" ไป " ขวา "เป็นอาร์เรย์" go left "ของจำนวนเต็ม"


ฉันจะปรับปรุงให้ดีขึ้นเล็กน้อย: "ptr เป็นชื่อที่หมายถึง" ไปทางขวา ... มัน)ตอนนี้ไปทางซ้าย ... มันคือ*"ตัวชี้ไปที่" ไปทางขวา ... มัน)ตอนนี้ไปทางซ้าย ... มันคือ(ออกมาไปทางขวา()เพื่อให้ "เพื่อฟังก์ชั่นซึ่งจะมีข้อโต้แย้งไม่" ไปขวา ... []"และผลตอบแทนอาร์เรย์ของ" ไปทางขวา;ท้ายเพื่อให้ไปทางซ้าย ... *"ชี้" ไปเหลือ ... int"จำนวนเต็ม"
Cacahuete Frito


2

นี่คือวิธีที่ฉันตีความมัน:

int *something[n];

หมายเหตุสำคัญ: ตัวดำเนินการตัวห้อย ( []) มีลำดับความสำคัญสูงกว่าตัวดำเนินการอ้างอิง ( *)

ดังนั้นที่นี่เราจะใช้[]ก่อนหน้า*ทำให้คำสั่งที่เทียบเท่ากับ:

int *(something[i]);

หมายเหตุเกี่ยวกับวิธีการประกาศจะทำให้ความรู้สึก: int numหมายถึงการnumเป็นint , int *ptrหรือint (*ptr)หมายถึง (มูลค่าptr) เป็นintซึ่งทำให้ตัวชี้ไปยังptrint

สิ่งนี้สามารถอ่านได้ว่า (ค่าของ (ค่าที่ดัชนี ith ของบางอย่าง)) เป็นจำนวนเต็ม ดังนั้น (ค่าที่ดัชนี ith ของบางอย่าง) คือ (ตัวชี้จำนวนเต็ม) ซึ่งทำให้บางสิ่งนั้นเป็นอาร์เรย์ของตัวชี้จำนวนเต็ม

ในอันที่สอง

int (*something)[n];

เพื่อให้เข้าใจถึงคำแถลงนี้คุณต้องคุ้นเคยกับข้อเท็จจริงนี้:

หมายเหตุเกี่ยวกับการเป็นตัวแทนตัวชี้ของอาร์เรย์: somethingElse[i]เทียบเท่า*(somethingElse + i)

ดังนั้นแทนที่somethingElseด้วย(*something)เราได้รับ*(*something + i)ซึ่งเป็นจำนวนเต็มตามประกาศ ดังนั้น(*something)ให้เราอาร์เรย์ซึ่งจะทำให้สิ่งที่เทียบเท่ากับ(ชี้ไปยังอาร์เรย์)


0

ฉันเดาว่าการประกาศครั้งที่สองนั้นทำให้หลายคนสับสน นี่เป็นวิธีง่ายๆในการทำความเข้าใจ

int B[8]ช่วยให้มีอาร์เรย์ของจำนวนเต็มคือ

Let 's ยังมีตัวแปรซึ่งจุดไป B. ตอนนี้ค่าที่ A เป็น B (*A) == Bคือ ดังนั้น A ชี้ไปที่อาร์เรย์ของจำนวนเต็ม ในคำถามของคุณ arr คล้ายกับ A

ในทำนองเดียวกันในint* (*C) [8]C เป็นตัวชี้ไปยังอาร์เรย์ของตัวชี้ไปยังจำนวนเต็ม


0
int *arr1[5]

ในการประกาศarr1นี้เป็นอาร์เรย์ของ 5 พอยน์เตอร์ต่อจำนวนเต็ม เหตุผล: วงเล็บเหลี่ยมมีความสำคัญมากกว่า * (ตัวดำเนินการยกเลิกการลงทะเบียน) และในประเภทนี้จำนวนแถวได้รับการแก้ไข (5 ที่นี่) แต่จำนวนคอลัมน์เป็นตัวแปร

int (*arr2)[5]

ในการประกาศarr2นี้เป็นตัวชี้ไปยังอาร์เรย์จำนวนเต็ม 5 องค์ประกอบ เหตุผล: ที่นี่ () วงเล็บเหลี่ยมมีความสำคัญมากกว่า [] และในประเภทนี้จำนวนแถวเป็นตัวแปร แต่จำนวนคอลัมน์ได้รับการแก้ไข (5 ที่นี่)


-7

ในตัวชี้ถึงจำนวนเต็มถ้าตัวชี้เพิ่มขึ้นมันจะไปเป็นจำนวนเต็มถัดไป

ในอาร์เรย์ของตัวชี้ถ้าตัวชี้เพิ่มขึ้นมันจะกระโดดไปยังอาร์เรย์ถัดไป


" ในอาเรย์ของพอยน์เตอร์ถ้าพอยน์เตอร์เพิ่มขึ้นมันจะกระโดดไปที่อาเรย์ถัดไป " ซึ่งผิดปกติ
alk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.