简短回答:
将此改为:
char **str;
成为这样:
char (*str)[10];
随后修复代码中的任何错误。 str
的类型与您的二维数组的类型不兼容。我自行将main()
的返回类型修复为int
(根据C标准,不允许使用void)。我还标记了您可能存在未定义行为的源:
#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]);
printf("\n%p ", &(*(*(str+4)+1)));
return 0;
}
需要注意的是最后的printf()
语句中,有一个多余的&(*(...))
部分是不必要的。可以去掉它,这样语句等同于:
printf("\n%p ", *(str+4)+1);
输出(系统相关)
0x7fff5fbff900
0x7fff5fbff900
0x7fff5fbff91e
0x7fff5fbff942
0x7fff5fbff929
非常(非常,非常)长的答案
您假设二维数组等同于指向指针的指针是错误的。它们仅在使用语法上类似。以下是数组 a
在内存中的大致布局。
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 | | | | | |
-----------------------------------------
请注意,第二行的起始地址,即
&a[1]
,比第一行,即
&a[0]
(也就是整个数组的起始地址)多了十个字节。以下演示了这一点:
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;
}
输出
&a = 0x7fff5fbff8e0
&a[0] = 0x7fff5fbff8e0
a[0] = 0x7fff5fbff8e0
&a[1] = 0x7fff5fbff8ea
a[1] = 0x7fff5fbff8ea
请注意,位于
a[1]
位置的地址距离数组开头有10字节(
0x0a
十六进制)。但是为什么呢?要回答这个问题,必须了解类型指针算术的基础知识。
类型指针算术是如何工作的?
在C和C++中,除
void
指针外,所有指针都有类型。它们与指针相关联的有一个基本数据类型。例如。
int *iptr = malloc(5*sizeof(int));
iptr
指向一个内存区域,该区域的大小被分配为五个整数。像处理数组一样访问它,看起来像这样:
iptr[1] = 1
但我们同样可以像这样解决它:
*(iptr+1) = 1
结果是一样的,将值1存储在第二个数组插槽中(0插槽为第一个)。我们知道解引用运算符 *
允许通过地址(存储在指针中的地址)进行访问。但是,(iptr+1)
如何知道跳过四个字节(如果您的int
类型为32位,则为八个字节,如果它们为64位,则为八个字节)以访问下一个整数插槽?
答案是:由于类型指针算术。编译器知道指针底层的类型宽度(在本例中,int
类型的宽度)。当您将标量值添加或减去指针时,编译器会生成适当的代码来考虑这个“类型宽度”。它也可以使用用户定义的类型。以下是演示:
#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;
}
输出
iptr = 0x7fff5fbff900
iptr+1 = 0x7fff5fbff904
pchr = 0x7fff5fbff8f0
pchr+1 = 0x7fff5fbff8f1
dptr = 0x7fff5fbff810
dptr+1 = 0x7fff5fbff87c
请注意,
iptr
和
iptr+1
之间的地址差异不是一个字节,而是四个字节(在我的系统上是
int
的宽度)。接下来,使用
pchr
和
pchr+1
演示了单个
char
的宽度为一个字节。最后,我们自定义的数据类型
Data
及其两个指针值
dptr
和
dptr+1
显示它的宽度为0x6C,即108个字节(由于结构包装和字段对齐,它可能更大,但在这个例子中我们很幸运它没有变得更大)。这是有道理的,因为该结构包含两个4字节的数据字段(一个
int
和一个
float
)以及一个100个元素宽的char缓冲区。
顺便说一下,反过来也是正确的,这经常被经验丰富的C/C++程序员所忽视。这就是类型指针的差异。如果您在有效内存的连续区域中有两个指定类型的有效指针:
int ar[10];
int *iptr1 = ar+1;
int *iptr5 = ar+5;
你认为执行以下操作会得到什么结果:
printf("%lu", iptr5 - iptr1);
答案是...
4
。你可能会说,这并不是什么大不了的事情?但请不要被表面所迷惑。当你使用指针算法来确定缓冲区中特定元素的偏移量时,这非常方便。
总之,当你有以下表达式时:
int ar[5];
int *iptr = ar;
iptr[1] = 1;
你可以理解为这与以下内容等价:
*(iptr+1) = 1
简单来说,就是“取出存储在iptr
变量中的地址,加上1个int
所占用的字节数,然后将值1
存储在通过返回地址引用的内存中。”
网站栏:这也可以。看看你能否想到为什么。
1[iptr] = 1
回到您(我们)的示例数组,现在看看当我们通过双指针引用相同的地址a
时会发生什么(这是完全不正确的,您的编译器应该至少警告您有关分配的问题):
char **str = a; // Error: Incompatible pointer types: char ** and char[5][10]
这么做行不通。但是假设它能够运行,char **
是指向字符指针的指针。这意味着该变量本身仅持有一个指针,它没有基本行宽等概念。因此,假设您将a
的地址放入双指针str
中。
char **str = (char **)(a); // Should NEVER do this, here for demonstration only.
char *s0 = str[0]; // what do you suppose this is?
我们的测试程序做了轻微更新:
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;
}
给我们以下结果:
&a = 0x7fff5fbff900
&a[0] = 0x7fff5fbff900
a[0] = 0x7fff5fbff900
&a[1] = 0x7fff5fbff90a
a[1] = 0x7fff5fbff90a
str = 0x7fff5fbff900
s0 = 0x656e6f
s1 = 0x6f77740000
好的,str
看起来就是我们想要的内容,但 s0
中的东西是什么呢?那是字母的ASCII字符值。哪些字母?查看一个不错的ASCII表可以快速得到它们是:
0x65 : e
0x6e : n
0x6f : o
这是单词“one”的反向拼写(由于我的系统处理多字节值的字节序问题而导致反向,但我希望问题很明显)。那第二个值呢:
0x6f : o
0x77 : w
0x74 : t
是的,这就是“两个”。那么为什么我们在数组中得到的字节是指针呢?
嗯...是的,正如我在评论中所说的那样。告诉你或者暗示你双重指针和二维数组是同义词的人是错误的。回想一下我们对类型指针算术的探索。记住这个:
str[1]
并且还有这个:
*(str+1)
这些术语是同义词。那么,str
指针的类型是什么?它所指向的类型是char
指针。因此,这两者之间的字节计数差异为:
str + 0
并且这个
str + 1
char*
的大小将是字节。在我的系统上,这是4个字节(我有32位指针)。 这就解释了为什么str[1]
中的表面地址是我们最初数组基础上的4个字节数据。
因此,回答你的第一个基本问题(是的,我们终于到了那一步)。
为什么str[3]
是 0xbf7f6292
答案:这个:
&str[3]
等同于这个:
(str + 3)
但是我们从上面知道
(str + 3)
只是存储在
str
中的地址,然后再加上3倍
str
类型的宽度所指向的
char *
,以字节为单位指向那个地址。好了,我们从你的第二个
printf
中知道了那个地址是什么:
0xbf7f6286
我们知道您系统上指针的宽度为4个字节(32位指针)。因此...
0xbf7f6286 + (3 * 4)
or....
0xbf7f6286 + 0x0C = 0xbf7f6292
a
,即使有两个维度,也不等同于指向指针的指针。 - WhozCraig