Trả lời Rất Ngắn:
Thay đổi này:
char **str;
Để có này:
char (*str)[10];
Và sửa chữa bất kỳ lỗi trong mã của bạn sau đó. Loại str
không tương thích với loại mảng hai chiều của bạn. Tôi đã tự do sửa chữa loại trả về là int
cho main()
(khoảng trống không được phép theo tiêu chuẩn C).Tôi cũng đánh dấu nguồn nơi bạn về mặt kỹ thuật có hành vi không xác định:
#include <stdio.h>
int main()
{
char a[5][10]={"one","two","three","four","five"};
char (*str)[10] = a;
printf("%p ", &a[0]);
printf("\n%p ", &str[0]);
printf("\n%p ", &str[3]);
printf("\n%p ", &str[1][56]); // this is undefined behaviour
printf("\n%p ", &(*(*(str+4)+1)));
return 0;
}
Bạn cũng nên lưu ý các printf()
tuyên bố cuối cùng có một thừa &(*(...))
rằng không cần thiết. Nó có thể bị tước trông như thế này, đó là tương đương:
printf("\n%p ", *(str+4)+1);
Output (Hệ thống phụ thuộc)
0x7fff5fbff900
0x7fff5fbff900
0x7fff5fbff91e
0x7fff5fbff942
0x7fff5fbff929
The Very (Rất, Rất) dài trả lời
Bạn không đúng với giả thiết rằng hai thứ nguyên al mảng tương đương với một con trỏ trỏ tới. Chúng tương tự như chỉ sử dụng cú pháp. Sau đây là một cách bố trí sơ bộ như thế nào mảng a
được miêu tả trong bộ nhớ
char a[5][10]
0 1 2 3 4 5 6 7 8 9
-----------------------------------------
0 | o | n | e | 0 | | | | | | |
-----------------------------------------
1 | t | w | o | 0 | | | | | | |
-----------------------------------------
2 | t | h | r | e | e | 0 | | | | |
-----------------------------------------
3 | f | o | u | r | 0 | | | | | |
-----------------------------------------
4 | f | i | v | e | 0 | | | | | |
-----------------------------------------
Lưu ý rằng địa chỉ bắt đầu của hàng thứ hai, đó là &a[1]
, là mười byte qua địa chỉ bắt đầu của dòng đầu tiên, &a[0]
(không phải ngẫu nhiên là địa chỉ bắt đầu của toàn bộ mảng). Sau đây chứng tỏ điều này:
int main()
{
char a[5][10] = { "one", "two", "three", "four", "five" };
printf("&a = %p\n", &a);
printf("&a[0] = %p\n", &a[0]);
printf("a[0] = %p\n", a[0]);
printf("&a[1] = %p\n", &a[1]);
printf("a[1] = %p\n", a[1]);
return 0;
}
Output
&a = 0x7fff5fbff8e0
&a[0] = 0x7fff5fbff8e0
a[0] = 0x7fff5fbff8e0
&a[1] = 0x7fff5fbff8ea
a[1] = 0x7fff5fbff8ea
Lưu ý rằng địa chỉ tại a[1]
là 10 byte (0x0a
hex) qua đầu của mảng. Nhưng tại sao? Để trả lời câu hỏi này, bạn phải hiểu các khái niệm cơ bản về số học con trỏ đã nhập.
Cách tính toán số học con trỏ?
Trong C và C++, tất cả các con trỏ ngoại trừ void
được nhập. Có một kiểu dữ liệu cơ bản được liên kết với con trỏ. Ví dụ.
int *iptr = malloc(5*sizeof(int));
điểm trên vào vùng bộ nhớ được phân bổ là kích thước năm số nguyên. Phát biểu nó như chúng ta thường giải quyết các mảng trông như thế này:
iptr[1] = 1;
nhưng chúng tôi có thể chỉ là một cách dễ dàng giải quyết nó như thế này:
*(iptr+1) = 1;
và kết quả sẽ là như nhau; lưu trữ giá trị 1 trong khe mảng thứ hai (0 là vị trí đầu tiên). Chúng ta biết toán tử dereference *
cho phép truy cập thông qua một địa chỉ (địa chỉ được lưu trữ trong con trỏ). Nhưng làm thế nào để (iptr+1)
biết bỏ qua bốn byte (nếu các loại int
của bạn là 32 bit, tám byte nếu chúng là 64bit) để truy cập vào khe nguyên tiếp theo?
Trả lời: vì số học con trỏ đã nhập.Trình biên dịch biết độ rộng, tính bằng byte, loại nằm dưới của con trỏ là (trong trường hợp này, chiều rộng của loại int
). Khi bạn cộng hoặc trừ các giá trị scaler thành/từ con trỏ, trình biên dịch sẽ tạo ra mã thích hợp cho tài khoản cho "chiều rộng kiểu" này. Nó cũng làm việc với các loại người dùng. Đây được thể hiện dưới đây:
#include <stdio.h>
typedef struct Data
{
int ival;
float fval;
char buffer[100];
} Data;
int main()
{
int ivals[10];
int *iptr = ivals;
char str[] = "Message";
char *pchr = str;
Data data[2];
Data *dptr = data;
printf("iptr = %p\n", iptr);
printf("iptr+1 = %p\n", iptr+1);
printf("pchr = %p\n", pchr);
printf("pchr+1 = %p\n", pchr+1);
printf("dptr = %p\n", dptr);
printf("dptr+1 = %p\n", dptr+1);
return 0;
}
Output
iptr = 0x7fff5fbff900
iptr+1 = 0x7fff5fbff904
pchr = 0x7fff5fbff8f0
pchr+1 = 0x7fff5fbff8f1
dptr = 0x7fff5fbff810
dptr+1 = 0x7fff5fbff87c
Chú ý sự khác biệt giữa địa chỉ iptr
và iptr+1
là không một byte; nó là bốn byte (chiều rộng của int
trên hệ thống của tôi). Tiếp theo, chiều rộng của một đơn char
được thể hiện với pchr
và pchr+1
dưới dạng một byte. Cuối cùng, kiểu dữ liệu tùy chỉnh của chúng tôi Data
với hai giá trị con trỏ của nó, dptr
và dptr+1
cho biết rằng nó là 0x6C hoặc rộng 108 byte. (nó có thể lớn hơn do đóng gói cấu trúc và sắp xếp trường, nhưng chúng tôi may mắn với ví dụ này là không). Điều đó có ý nghĩa, vì cấu trúc chứa hai trường dữ liệu 4 byte (một int
và float
) và một bộ đệm char 100 phần tử rộng.
Điều ngược lại cũng đúng, bằng cách này và thường là không phải được các lập trình viên C/C++ có kinh nghiệm xem xét. Nó được nhập vào con trỏ differencing. Nếu bạn có hai con trỏ hợp lệ của một loại quy định trong một khu vực tiếp giáp của bộ nhớ có giá trị:
int ar[10];
int *iptr1 = ar+1;
int *iptr5 = ar+5;
gì bạn cho rằng bạn sẽ có được kết quả là từ:
printf("%lu", iptr5 - iptr1);
Câu trả lời là .. 4
. Whoopee, bạn nói. Không phải là một thỏa thuận lớn? Bạn không tin rằng nó. Đây là cực kỳ tiện dụng khi sử dụng số học con trỏ để xác định độ lệch trong bộ đệm của một phần tử cụ thể.
Nói tóm lại, khi bạn có một biểu hiện như thế này:
int ar[5];
int *iptr = ar;
iptr[1] = 1;
Bạn có thể biết nó tương đương với:
*(iptr+1) = 1;
nào, về lay-con người, có nghĩa là "Hãy địa chỉ được giữ trong biến số iptr
, thêm 1 * (chiều rộng của một byte là int
byte), sau đó lưu trữ giá trị 1
trong bộ nhớ được đăng ký lại thông qua địa chỉ được trả lại. "
Thanh trang web: Thao tác này cũng sẽ hoạt động.Xem nếu bạn có thể nghĩ tại sao
1[iptr] = 1;
Về mảng mẫu của bạn (chúng tôi), Bây giờ hãy nhìn vào những gì sẽ xảy ra khi chúng tôi đề cập đến cùng một địa chỉ của a
, nhưng thông qua một con trỏ kép (hoàn toàn không chính xác và trình biên dịch của bạn ít nhất phải cảnh báo bạn về việc chuyển nhượng):
char **str = a; // Error: Incompatible pointer types: char ** and char[5][10]
Điều đó không hiệu quả. Nhưng cho phép giả sử một lúc nó đã làm. char **
là một con trỏ trỏ đến một con trỏ tới char. Điều này có nghĩa là bản thân biến không giữ gì hơn một con trỏ. Nó không có khái niệm về chiều rộng hàng cơ sở, vv Vì vậy giả sử bạn đặt địa chỉ của a
trong con trỏ kép str
.
char **str = (char **)(a); // Should NEVER do this, here for demonstration only.
char *s0 = str[0]; // what do you suppose this is?
nhẹ cập nhật cho chương trình thử nghiệm của chúng tôi:
int main()
{
char a[5][10] = { "one", "two", "three", "four", "five" };
char **str = (char **)a;
char *s0 = str[0];
char *s1 = str[1];
printf("&a = %p\n", &a);
printf("&a[0] = %p\n", &a[0]);
printf("a[0] = %p\n", a[0]);
printf("&a[1] = %p\n", &a[1]);
printf("a[1] = %p\n", a[1]);
printf("str = %p\n", str);
printf("s0 = %p\n", s0);
printf("s1 = %p\n", s1);
return 0;
}
Cung cấp cho chúng ta những kết quả sau:
&a = 0x7fff5fbff900
&a[0] = 0x7fff5fbff900
a[0] = 0x7fff5fbff900
&a[1] = 0x7fff5fbff90a
a[1] = 0x7fff5fbff90a
str = 0x7fff5fbff900
s0 = 0x656e6f
s1 = 0x6f77740000
Vâng, str
có vẻ như nó là những gì chúng ta muốn, nhưng điều đó là những gì trong s0
? Tại sao, đó là giá trị ký tự ASCII cho chữ cái. Cái nào? Một kiểm tra nhanh chóng của một phong nha ASCII table cho thấy là:
0x65 : e
0x6e : n
0x6f : o
Thats từ "một" ngược lại (ngược lại là do việc xử lý endian các giá trị đa byte trên hệ thống của tôi, nhưng tôi hy vọng vấn đề là rõ ràng gì về điều đó giá trị thứ hai:.
0x6f : o
0x77 : w
0x74 : t
Yep, đó là "hai" Vậy tại sao chúng ta nhận được byte trong mảng của chúng tôi như con trỏ
Hmmm .. Yeah, như tôi đã nói ở.? ai đó nói với bạn hoặc hin ted với bạn rằng con trỏ đôi và mảng hai chiều đồng nghĩa là không chính xác. Hãy nhớ lại sự gián đoạn của chúng tôi vào số học con trỏ đã nhập. Hãy nhớ rằng, điều này:
str[1]
và điều này:
*(str+1)
là đồng nghĩa. Tốt. loại loại của con trỏ str
là gì? Loại nó trỏ đến là con trỏ char
. Vì vậy, sự khác biệt đếm byte giữa này:
str + 0
và điều này
str + 1
sẽ là kích thước của một char*
. Trên hệ thống của tôi là 4 byte (tôi có con trỏ 32 bit). Điều này giải thích lý do tại sao địa chỉ rõ ràng trong str[1]
là dữ liệu bốn byte vào cơ sở mảng ban đầu của chúng tôi.
Do đó, trả lời câu hỏi cơ bản đầu tiên của bạn (có, chúng tôi cuối cùng là đã nhận được điều đó).
Tại sao str[3]
là 0xbf7f6292
trả lời: Đây:
&str[3]
tương đương với điều này:
(str + 3)
Nhưng chúng ta biết từ trên (str + 3)
đó chỉ là địa chỉ lưu trữ trong str
, sau đó thêm 3x chiều rộng của loại str
điểm vào, là char *
, tính bằng byte đến địa chỉ đó. Tốt. chúng ta đã biết từ bạn thứ hai printf
gì địa chỉ đó là:
0xbf7f6286
Và chúng ta biết chiều rộng của một con trỏ trên hệ thống của bạn là 4 byte (32 con trỏ bit). Do đó ...
0xbf7f6286 + (3 * 4)
hay ....
0xbf7f6286 + 0x0C = 0xbf7f6292
mảng của bạn 'a', ngay cả với hai chiều, không đồng nghĩa với một con trỏ đến con trỏ. – WhozCraig