Tạo một con trỏ đến mảng hai chiều


119

Tôi cần một con trỏ đến một mảng 2 chiều tĩnh. Làm thế nào là điều này được thực hiện?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

Tôi nhận được tất cả các loại lỗi như:

  • cảnh báo: gán từ loại con trỏ không tương thích
  • giá trị số con không phải là mảng hay con trỏ
  • lỗi: sử dụng thành viên mảng linh hoạt không hợp lệ


1
@ JohannesSchaub-litb Điều đó không tồn tại nữa. (? Làm thế nào để một lần nữa xem nó ... Tôi biết cho thành viên đại diện thấp có thể xem nó, nhưng tôi đã quên như thế nào ...)
Mateen Ulhaq

@muntoo: Đây là bản sao của nó: gist.github.com/sharth/ede13c0502d5dd8d45bd
Bill Lynch

Câu trả lời:


139

Ở đây bạn muốn tạo một con trỏ đến phần tử đầu tiên của mảng

uint8_t (*matrix_ptr)[20] = l_matrix;

Với typedef, cái này trông gọn gàng hơn

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Sau đó, bạn có thể tận hưởng cuộc sống một lần nữa :)

matrix_ptr[0][1] = ...;

Hãy coi chừng thế giới con trỏ / mảng trong C, có nhiều sự nhầm lẫn xung quanh điều này.


Biên tập

Xem lại một số câu trả lời khác tại đây, vì trường nhận xét quá ngắn để thực hiện ở đó. Nhiều lựa chọn thay thế đã được đề xuất, nhưng nó không chỉ ra cách chúng hoạt động. Đây là cách họ làm

uint8_t (*matrix_ptr)[][20] = l_matrix;

Nếu bạn sửa lỗi và thêm toán tử address-of &như trong đoạn mã sau

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Sau đó, cái đó tạo một con trỏ đến một kiểu mảng không đầy đủ các phần tử của kiểu mảng 20 uint8_t. Bởi vì con trỏ là một mảng của mảng, bạn phải truy cập nó bằng

(*matrix_ptr)[0][1] = ...;

Và bởi vì nó là một con trỏ đến một mảng chưa hoàn chỉnh, bạn không thể làm như một phím tắt

matrix_ptr[0][0][1] = ...;

Bởi vì lập chỉ mục yêu cầu phải biết kích thước của loại phần tử (lập chỉ mục ngụ ý thêm một số nguyên vào con trỏ, vì vậy nó sẽ không hoạt động với các kiểu không hoàn chỉnh). Lưu ý rằng điều này chỉ hoạt động trong C, bởi vì T[]T[N]là các loại tương thích. C ++ không có khái niệm về các kiểu tương thích , và vì vậy nó sẽ từ chối mã đó, bởi vì T[]T[10]là các kiểu khác nhau.


Phương án thay thế sau hoàn toàn không hoạt động, bởi vì kiểu phần tử của mảng, khi bạn xem nó dưới dạng mảng một chiều, thì không uint8_t , nhưnguint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Sau đây là một sự thay thế tốt

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Bạn truy cập nó bằng

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Nó có lợi ích là nó duy trì kích thước bên ngoài. Vì vậy, bạn có thể áp dụng sizeof trên nó

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Có một câu trả lời khác sử dụng thực tế là các mục trong một mảng được lưu trữ liên tục

uint8_t *matrix_ptr = l_matrix[0];

Bây giờ, điều đó chính thức chỉ cho phép bạn truy cập các phần tử của phần tử đầu tiên của mảng hai chiều. Đó là, điều kiện sau giữ

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Bạn sẽ nhận thấy nó có thể hoạt động tối đa 10*20-1, nhưng nếu bạn sử dụng phân tích bí danh và các tối ưu hóa tích cực khác, một số trình biên dịch có thể đưa ra giả định có thể phá vỡ mã đó. Phải nói rằng, tôi chưa bao giờ gặp phải một trình biên dịch nào bị lỗi trên nó (nhưng một lần nữa, tôi đã không sử dụng kỹ thuật đó trong mã thực), và thậm chí C FAQ có chứa kỹ thuật đó (với một cảnh báo về UB'ness của nó ), và nếu bạn không thể thay đổi kiểu mảng, đây là lựa chọn cuối cùng để cứu bạn :)


1 - Thông tin thoải mái trên int (*) [] [20] sự cố - không thể làm điều đó trong C ++
Faisal Vali

@litb, tôi xin lỗi nhưng điều này là sai vì giải pháp của bạn không cung cấp bất kỳ phân bổ bộ nhớ nào cho mảng.
Rob Wells

2
@Rob, tôi không hiểu lắm về bạn. bộ nhớ trong tất cả các trường hợp này được cung cấp bởi chính mảng l_matix. Các con trỏ tới chúng nhận lưu trữ từ bất kỳ nơi nào chúng được khai báo trong và dưới dạng (ngăn xếp, phân đoạn dữ liệu tĩnh, ...).
Johannes Schaub - litb

Chỉ tò mò tại sao chúng ta cần địa chỉ "&" của l_matrix?
electro

1
@Sohaib - không, chỉ tạo ra một con trỏ. Bạn có thể đã nhầm lẫn nó với uint8_t *d[20], tạo ra một mảng 3 con trỏ đến uint8_t, nhưng điều đó sẽ không hoạt động trong trường hợp này.
Palo

27

Để hiểu đầy đủ điều này, bạn phải nắm được các khái niệm sau:

Mảng không phải là con trỏ!

Trước hết (Và nó đã được giảng đủ), mảng không phải là con trỏ . Thay vào đó, trong hầu hết các trường hợp sử dụng, chúng 'phân rã' thành địa chỉ cho phần tử đầu tiên của chúng, có thể được gán cho một con trỏ:

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

int *p = a; // p now points to a[0]

Tôi cho rằng nó hoạt động theo cách này để nội dung của mảng có thể được truy cập mà không cần sao chép tất cả chúng. Đó chỉ là một hành vi của các kiểu mảng và không có nghĩa là chúng giống nhau.



Mảng đa chiều

Mảng đa chiều chỉ là một cách để 'phân vùng' bộ nhớ theo cách mà trình biên dịch / máy có thể hiểu và vận hành.

Ví dụ, int a[4][3][5] = một mảng chứa 4 * 3 * 5 (60) 'khối' bộ nhớ có kích thước số nguyên.

Lợi thế so với sử dụng int a[4][3][5]đồng bằngint b[60] là giờ đây chúng đã được 'phân vùng' (Dễ dàng làm việc hơn với 'các khối' của chúng, nếu cần) và chương trình hiện có thể thực hiện kiểm tra giới hạn.

Trên thực tế, int a[4][3][5]được lưu trữ giống hệt như int b[60]trong bộ nhớ - Sự khác biệt duy nhất là chương trình hiện quản lý nó như thể chúng là các thực thể riêng biệt có kích thước nhất định (Cụ thể là bốn nhóm ba nhóm năm).

Lưu ý: Cả hai int a[4][3][5]int b[60]đều giống nhau trong bộ nhớ và sự khác biệt duy nhất là cách chúng được ứng dụng / trình biên dịch xử lý

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

Từ đó, bạn có thể thấy rõ ràng rằng mỗi "phân vùng" chỉ là một mảng mà chương trình theo dõi.



Cú pháp

Bây giờ, mảng khác với con trỏ về mặt cú pháp . Cụ thể, điều này có nghĩa là trình biên dịch / máy sẽ xử lý chúng theo cách khác nhau. Điều này có vẻ không có trí tuệ, nhưng hãy xem điều này:

int a[3][3];

printf("%p %p", a, a[0]);

Ví dụ trên in cùng một địa chỉ bộ nhớ hai lần, như sau:

0x7eb5a3b4 0x7eb5a3b4

Tuy nhiên, chỉ một có thể được gán trực tiếp cho một con trỏ :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Tại sao không a thể gán cho một con trỏ nhưng a[0] có thể?

Điều này, đơn giản, là hệ quả của các mảng nhiều chiều và tôi sẽ giải thích tại sao:

Ở cấp độ ' a', chúng ta vẫn thấy rằng chúng ta có một 'không gian' khác để mong đợi. Ở cấp độ của 'a[0]Tuy nhiên, ', chúng ta đã ở trong kích thước hàng đầu, theo như chương trình có liên quan, chúng ta chỉ đang xem xét một mảng bình thường.

Bạn có thể hỏi:

Tại sao lại có vấn đề nếu mảng là nhiều chiều liên quan đến việc tạo một con trỏ cho nó?

Tốt nhất hãy nghĩ theo cách này:

Một 'phân rã' từ một mảng nhiều chiều không chỉ là một địa chỉ mà còn là một địa chỉ với dữ liệu phân vùng (AKA nó vẫn hiểu rằng dữ liệu cơ bản của nó được tạo từ các mảng khác), bao gồm các ranh giới được thiết lập bởi mảng bên ngoài kích thước đầu tiên.

Logic 'phân vùng' này không thể tồn tại trong một con trỏ trừ khi chúng tôi chỉ định nó:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

Nếu không, ý nghĩa của các thuộc tính sắp xếp của mảng sẽ bị mất.

Cũng lưu ý việc sử dụng dấu ngoặc đơn xung quanh *p: int (*p)[5][95][8]- Điều đó để xác định rằng chúng tôi đang tạo một con trỏ với các giới hạn này, không phải một mảng con trỏ với các giới hạn này:int *p[5][95][8]



Phần kết luận

Hãy xem lại:

  • Mảng phân rã thành địa chỉ nếu chúng không có mục đích khác trong ngữ cảnh đã sử dụng
  • Mảng đa chiều chỉ là mảng của mảng - Do đó, địa chỉ 'bị phân rã' sẽ mang gánh nặng "Tôi có thứ nguyên phụ"
  • Dữ liệu thứ nguyên không thể tồn tại trong một con trỏ trừ khi bạn cung cấp cho nó .

Tóm lại: mảng đa chiều phân rã thành các địa chỉ có khả năng hiểu nội dung của chúng.


1
Phần đầu tiên của câu trả lời là tuyệt vời, nhưng phần thứ hai thì không. Điều này không chính xác:, int *p1 = &(a[0]); // RIGHT !thực ra nó giống hệtint *p1 = a;
2501

@ 2501 Cảm ơn bạn đã phát hiện ra lỗi đó, tôi đã sửa nó. Tôi không thể nói chắc chắn tại sao ví dụ xác định "quy tắc" này cũng bất chấp nó. Đáng nói lại là chỉ vì hai thực thể có thể được hiểu là con trỏ và mang lại cùng giá trị, không có nghĩa là chúng mang cùng một ý nghĩa.
Super Cat

7

Trong

int *ptr= l_matrix[0];

bạn có thể truy cập như

*p
*(p+1)
*(p+2)

sau khi tất cả các mảng 2 chiều cũng được lưu trữ dưới dạng 1-d.


5

G'day,

Khai báo

static uint8_t l_matrix[10][20];

đã dành bộ nhớ cho 10 hàng gồm 20 vị trí unit8_t, tức là 200 vị trí có kích thước uint8_t, với mỗi phần tử được tìm thấy bằng cách tính 20 x hàng + cột.

Vì vậy, không

uint8_t (*matrix_ptr)[20] = l_matrix;

cung cấp cho bạn những gì bạn cần và trỏ đến phần tử không cột của hàng đầu tiên của mảng?

Chỉnh sửa: Suy nghĩ về điều này xa hơn một chút, không phải tên mảng, theo định nghĩa, là một con trỏ? Nghĩa là, tên của một mảng là từ đồng nghĩa với vị trí của phần tử đầu tiên, tức là l_matrix [0] [0]?

Edit2: Như những người khác đã đề cập, không gian bình luận hơi quá nhỏ để thảo luận thêm. Dù sao:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

không cung cấp bất kỳ phân bổ dung lượng nào cho mảng được đề cập.

Như đã đề cập ở trên, và theo định nghĩa của tiêu chuẩn, tuyên bố:

static uint8_t l_matrix[10][20];

đã dành ra 200 vị trí tuần tự của loại uint8_t.

Tham chiếu đến l_matrix bằng cách sử dụng các câu lệnh có dạng:

(*l_matrix + (20 * rowno) + colno)

sẽ cung cấp cho bạn nội dung của phần tử colno'th được tìm thấy trong rowno hàng.

Tất cả các thao tác với con trỏ sẽ tự động tính đến kích thước của đối tượng được trỏ tới. - K&R Phần 5.4, tr.103

Đây cũng là trường hợp xảy ra nếu bất kỳ sự dịch chuyển vùng đệm hoặc căn chỉnh byte nào liên quan đến việc lưu trữ đối tượng trong tầm tay. Trình biên dịch sẽ tự động điều chỉnh cho những điều này. Theo định nghĩa của tiêu chuẩn C ANSI.

HTH

chúc mừng,


1
uint8_t (* matrix_ptr) [] [20] << khung đầu tiên phải được bỏ qua, đúng là uint8_t (* matrix_ptr) [20]
Aconcagua

5

Trong C99 (được hỗ trợ bởi clang và gcc), có một cú pháp khó hiểu để chuyển mảng đa chiều tới các hàm bằng cách tham chiếu:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

Không giống như một con trỏ đơn giản, điều này gợi ý về kích thước mảng, về mặt lý thuyết cho phép trình biên dịch cảnh báo về việc chuyển mảng quá nhỏ và phát hiện ra rõ ràng truy cập ngoài giới hạn.

Đáng buồn thay, nó không sửa chữa sizeof()và các trình biên dịch dường như chưa sử dụng thông tin đó, vì vậy nó vẫn là một sự tò mò.


1
Câu trả lời này gây hiểu lầm: Điều đó không làm cho đối số trở thành một mảng có kích thước cố định, nó vẫn là một con trỏ. static 10là một loại đảm bảo rằng có ít nhất 10 phần tử, điều này có nghĩa là kích thước không cố định.
bluss

1
@bluss câu hỏi là về một con trỏ, vì vậy tôi không thấy cách trả lời bằng con trỏ (lưu ý bằng cách tham khảo ) gây hiểu lầm. Mảng có kích thước cố định theo quan điểm của hàm, vì quyền truy cập vào các phần tử vượt quá các giới hạn này là không xác định.
Kornel

Tôi không nghĩ rằng truy cập vượt quá 10 là không xác định, tôi không thể thấy bất kỳ điều gì cho thấy điều đó.
bluss

Câu trả lời này dường như gợi ý rằng nếu không có từ khóa static, mảng sẽ không được chuyển qua tham chiếu, điều này không đúng. Mảng vẫn được truyền bằng tham chiếu. Câu hỏi ban đầu hỏi về một trường hợp sử dụng khác - truy cập các phần tử của mảng 2D bằng con trỏ bổ sung trong cùng một hàm / không gian tên.
Palo

4

Bạn luôn có thể tránh loay hoay với trình biên dịch bằng cách khai báo mảng là tuyến tính và tự mình thực hiện tính toán chỉ số (row, col) cho mảng.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

đây là những gì trình biên dịch sẽ làm.


1
Đây là những gì trình biên dịch C làm. C không thực sự có bất cứ khái niệm thực sự của một "mảng" - các [] ký hiệu chỉ là cú pháp đường cho con trỏ số học
Ken Keenan

7
Giải pháp này có nhược điểm là không bao giờ tìm ra cách thích hợp để thực hiện.
Craig McQueen

2

Cú pháp cơ bản của việc khởi tạo con trỏ trỏ đến mảng đa phương là

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

Cú pháp cơ bản để gọi nó là

(*pointer_name)[1st index][2nd index][...]

Đây là một ví dụ:

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

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

Đầu ra là:

Subham
Messi

Nó đã làm việc...


1

Bạn có thể làm như thế này:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

1
Điều này không chiếm 10 * 20 byte ram? (tôi đang sử dụng một bộ vi điều khiển)
Dill

Nó sẽ chiếm 4 byte hoặc bất kỳ kích thước nào mà một con trỏ lớn trong hộp của bạn. Nhưng hãy nhớ nếu bạn có cái này, bạn phải lập chỉ mục với matrix_ptr [0] [x] [y] hoặc (* matrix_ptr) [x] [y]. Đó là việc giải thích trực tiếp và word-by-word của "con trỏ đến mảng hai chiều": p
Johannes Schaub - litb

Cảm ơn litb, tôi quên đề cập đến cách truy cập nó. Không có điểm để chỉnh sửa câu trả lời của tôi kể từ khi bạn đã làm một công việc tuyệt vời với câu trả lời của bạn :)
Nick Dandoulakis

Vì vậy, điều này có chiếm 10 * 20 byte RAM hay không?
Danijel

@Danijel, vì nó là một con trỏ đến mảng hai chiều, nó sẽ chỉ chiếm 4 byte hoặc bất kỳ kích thước nào mà một con trỏ có trong hộp của bạn, tức là 16 bit, 32 bit, 64 bit, v.v.
Nick Dandoulakis

1

Bạn muốn một con trỏ đến phần tử đầu tiên, vì vậy;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}

0

Bạn cũng có thể thêm phần bù nếu muốn sử dụng các chỉ mục phủ định:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

Nếu trình biên dịch của bạn đưa ra lỗi hoặc cảnh báo, bạn có thể sử dụng:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;

Xin chào. Câu hỏi này được gắn thẻ với c, vì vậy câu trả lời phải bằng cùng một ngôn ngữ. Vui lòng quan sát các thẻ.
2501
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.