指针、多维数组和地址

5
假设我们有:int A [5] [2] [3]; 现在,如果我这样做:A[1][0][0] = 4; 是什么意思:
1.) A [1]和A [1][0]是指针吗?
2.) 如果A[1]是指针,那么它将存储指针A[1][0]的地址?
3.) 如果A[1][0]是指针,则它将存储A[1][0][0]的地址,A[1][0][0]不是指针而只是存储值4的变量?
如果上述观点正确,那么为什么以下代码会给我们相同的整数地址:
int main(void)
{
        int A [5] [2] [3];
    A[1][0][0]=4;

    printf("%d\n\n", A[1]);
    printf("%d\n\n", A[1][0]);
    printf("%d\n\n",&A[1][0][0]);

        system("pause");
}

在这里,我假设A [1]是一个指向另一个指针A [1] [0]的指针,因此存储指针A [1] [0]的地址。而A [1] [0]是一个指向变量A [1] [0] [0]的指针,因此存储变量A [1] [0] [0]的地址。
请帮助我!

虽然与你的问题无关,但“printf()”格式“%d”预期是一个'int',如果你没有提供它,就会出现未定义行为,这是不好的。你给了它'A [1]'一个2x3-int数组衰减成指向int的指针, 'A [1] [0]'一个3-int数组衰减成指向int的指针,以及'&A [1] [0] [0]'一个指向int的指针。如果sizeof(int)<sizeof(int *),则可能只会看到部分指针值;如果sizeof(int)> sizeof(int *),则可能会看到垃圾;但适当的Machiavellian编译器可以发出代码来擦除您的硬盘并仍符合标准。 - mlp
6个回答

4
为了正确回答您的问题,请了解行主序,这是多维数组在C中存储的方式。Wikipedia article有点简略,但其中一个可能更清晰:

http://webster.cs.ucr.edu/AoA/Windows/HTML/Arraysa2.html http://archive.gamedev.net/archive/reference/articles/article1697.html http://www.ibiblio.org/pub/languages/fortran/append-c.html

这里还有一个相关的问题在Stack Overflow上

回答你的问题,假设你知道行主序存储的工作原理:

int A[5][2][3] 声明了一个连续的内存区域,长度为 5*2*3 个整数:五个大小为两个大小为三个整数的数组。这些数组在线性内存中相邻存储。

&A[0][0][0] == A
&A[0][0][1] == A+1
&A[0][1][0] == A+(1*3)
&A[3][1][2] == A+(3*(2*3))+(1*3)+2

A[1] 技术上不是指针,而是一个数组。它是一个 int [2][3] 数组。但我发现考虑 A[5][2][3] 是一个长度为 30 的扁平内存区域比考虑数组更清晰易懂。

A[0][0][0] is the first integer in that region. 
A[0][0][1] is the second integer. 
A[0][0][2] is the third integer. 
A[0][1][0] is the fourth integer in this flat region. 
A[0][1][1] is the fifth integer. 
And so on until A[1][0][0] is the eleventh integer. 

因此,A [1] [0] [0] 的地址比 A [0] [0] [0] 多十个整数;也就是说,&A [1] [0] [0] - &A [0] [0] [0] == 10。因为 C 语言在数组和指针之间的区别上非常宽松,所以当您在表达式中使用A [1]时,它会被解释为地址,即使它实际上意味着“一个由五个数组组成的数组,每个数组又由两个包含三个整数的数组组成”,而这又是“一个由两个包含三个整数的数组组成”的数组。

结果是A [1]并不是存储指针,它本身就是一个指针。您的多维数组中从&A [0] [0] [0]&A [5] [2] [3]-1的所有内存地址都存储了一个整数。

您在第二点和第三点中考虑的是指向数组的指针(arrays of pointers to arrays),那是另外一回事。

这个问题最好用图片来解释,因此你应该找一本适当的关于C数组的教材或文章。

通常,在学习C语言中指针和数组时,我建议您暂时忘记语言本身,并假装自己是Dennis Ritchie在一台只有56kb平面RAM的PDP-11计算机上发明C语言。拿一张大的方格纸,连续编号每个单元格,假装它代表你的RAM,每个单元格都是一个字节,你可以用铅笔和纸来进行指针运算

C语言就是在那种环境下发明的,了解其起源将使现代语言更加合理。

顺便提一下,当我尝试写这个答案时,Stack Overflow的标记语言反复更改并破坏了上面我的数组示例中的索引。因此,如果你看到那里的任何数字似乎超出了它们的数组范围,那是编辑器引入的错误。


以下内容是否正确:&A[0][0][0] == A 和 &A[0][0][0] == A+1 - John Nash
你能告诉我计算&A[0][1][0] == A+(1*3)公式是怎么得出的吗?有没有链接可以找到这个公式? - John Nash
1
@JohnNash 是的,我链接的所有文章都解释了那个数学是如何工作的。 - Crashworks
感谢您的解释并为我提供了这些有用的链接。 - John Nash

1

C语言中的多维数组不使用指针。虽然指针到指针到指针的访问可能看起来类似,但实际数据可能不是连续的,并且需要存储所有地址的开销。在C中访问多维数组是一种语法糖:

char a[5][7][9]
a[d][h][w]  <<==>>  ((char*)a)[((9*7*d)+(9*h)+w] 

C数组都具有一个共同的特性,即它们会衰变(或自动转换为指向数组第一个元素的指针)。因此,
a[1]  (char[7][9])    --decay-->   ((*char)[5][9]) pointer to char array
&a[1] ((*char)[5][9])  no decay

两者是等价的,因为在后者中,您明确地“衰减”指针,而在第一个中则会自动发生。

但是我们如何才能得到相同的输出结果呢?int main(void) { int A[5][2][3]; A[1][0][0] = 4;printf("%d\n\n", A[1]); printf("%d\n\n", &A[1]); system("pause");} - John Nash
我无法格式化它。但问题是:A[1]和&A[1]会给出相同的输出吗? - John Nash
当情况需要指针时,它们会转换为指向它们的第一个元素的指针。 - Dave
你能解释一下 a[1] (char[7][9]) 如何衰变为 ((*char)[5][9]) 吗? - John Nash

1

如果您使用动态数组(即使用malloc/calloc分配的数组),那么您的假设是正确的。

然而,静态数组是作为一块连续的内存分配的,并且只是指向第一个元素的指针。当您写A[X][Y][Z]时,它基本上等同于*(A + X*YSIZE*ZSIZE + Y*SIZE + Z),而不是*(*(*(A+X) + Y) + Z)。这样可以更快地访问数据(您不需要访问中间指针),但需要将所有数据分配在一个块中并具有规则的大小。

此处提供有关C语言中静态和动态数组不可互换性的更多信息。


1

变量A是5 * 2 * 3个整数,作为一个块一起分配的。(即30个整数)。

在声明'int A [5] [2] [3];'时没有涉及指针 - 唯一预留的空间是用于保存30个整数值。

当您使用A和下标编写表达式时,因为您已经说明了有3个维度,所以必须按顺序提供所有3个以指定要访问或更改的int值。如果您使用少于3个下标,则只部分指定了要访问的内容;约定是这样的引用被视为请求整个空间中相关部分的地址。


但是为什么A [1]和&A [1]会给我们相同的输出:它们都给出了相同的整数地址。 - John Nash
@John Nash:“A [1]”是一个2x3的“int”数组,在指针上下文中,它会衰减为该数组的地址。“&A [1]”是“'A [1]'的地址”。 - mlp
这只是C语言创建者采用的一种约定。他们认为这是对表达式最合理和相对“自然”的解释。 - Art Swri

0

阅读行主序相关内容。

是的,那并没有帮助我,我曾经认为C语言是列主序,但现在我忘记了所有这些东西的含义。


0

这可能有点奇怪,但是这是我的解释。想象一下你有六个不同的人,编号从1到6,他们按照从1到6的顺序排列。如果你告诉这个团队(六个人)被分成两组,其中前三个(1-3)在A组,其余的(4-6)在B组。

[1 2 3] [4 5 6]

那么,如果我告诉你谁是团队中的第一个人?你会说第一个人!但是如果我问你谁是团队中A组的第一个成员?它是相同的,第一个人!

此外,如果我告诉你谁是团队中的第四个成员?你会说第四个!那么,如果我问你谁是团队中B组的第一个成员?它是相同的,第四个。


同样的故事发生在你身上; A [1] 是指向大数组(The Team)开头的指针,其中 A [1] [0] 告诉指向大数组(A [1])内第一个内部数组(THE FIRST GROUP)的 开始 ,它是相同的! 然后你说,&A [1] [0] [0],就像是去问驻留在大数组中的第一个内部数组的第一个成员,你的号码是多少?然后,他会回答相同的话。

不同的是指针的类型和解释方式,但它们的值是相同的。 这是因为数组以连续的方式存储元素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接