在C语言中解引用包含指针的数组

3
我写了一个程序,其中涉及两行整数定期交换位置。因此,我分配了两个单独的指向整数的指针来模拟所需的行。然后,我将这两个指针放入一个常量指针数组中,以便轻松地进行交换。
我遇到的问题是完全错误的。以这个为例:
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char* a = malloc(sizeof(char)*5);
    char* temp[] = {a};
    int i, h, k = 'a';

    for(i = 0; i < 5; i++)
        a[i] = k++;

    for(i = 0; i < 5; i++)
        printf("%c ", temp[0][i]);

    free(a);
    return 0;
}

使用输出:a b c d e IdeOne 我无意中尝试通过二次间接引用“[][]”访问静态数组中包含的数组的成员:

我没有太在意这件事情,因为一切都编译和运行得很完美,甚至在输出时给出了预期的结果。问题是当我运行内存分析时,发现我得到了大量的寻址错误和假定的内存泄漏。

所以,为了开始调试过程,我想来这里问一下这为什么有效并且它是否是有效的C语法。我的印象是:

temp[0]

会解引用为char*类型的指针a。需要再次对a进行解引用才能访问其成员。此外,我认为:

temp[row][col]

Is translated to:

*(*(temp + row*col) + col)

在运行时。因此,temp[0][i] 的双重引用应该给出从temp的偏移量,而不是访问a


更有趣的是:

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

int main(void) {
    char* a = malloc(sizeof(char)*5);
    char* b = malloc(sizeof(char)*5);
    char* temp[] = {a, b};
    int i, h, k = 'a', j = 'A'; 

    for(i = 0; i < 5; i++){
        a[i] = k++;
        b[i] = j++;
    }

    for(i = 0; i < 5; i++){
        for(h = 0; h < 5; h++)
            printf("%c ", temp[i][h]);
        puts(""); 
    }

    free(a);
    free(b);
    return 0;
}    

IdeOne运行时无法编译。但是,在Microsoft Visual Studio Professional 2013和c99模式下使用gcc,这段代码确实可以编译。
对于我试图做的事情,有什么想法或建议吗?也就是说,如何访问包含在静态数组temp中的成员ab,而不使用非常丑陋的语法?
*(temp[i] + sizeof(char)*h)

3
你的第一个程序很好,你对寻址的推理是正确的(除了“row*col”计算)。第二个程序访问了temp的五个索引,但只有两个可用。 - hamstergene
数组不会被“解引用”。 - alk
@alk,数组是指向内存的常量指针,因此在使用时会被解引用。例如,一些 int* foo[5] 数组实际上与指向一个内存块 sizeof(int)*5 的常量整数数组相同。因此,foo[2] 指向第三个整数,但也可以使用 *(foo+2) 解引用到相同的成员。也许你在标题中没有使用正确的术语,但每个人似乎都理解了这个想法。 - sherrellbc
你说对了,我本来不是想加星号的。谢谢。 - sherrellbc
哦,伙计,真是个追求完美的人。再次感谢你。我指的是foo是一个常量指针,因此*(foo+2)将用户引导到第三个整数。 - sherrellbc
显示剩余5条评论
3个回答

3
temp[row][col]不等同于*(*(temp + row*col) + col),而实际上等同于*(*(temp + row) + col)
您的第二个示例代码是错误的,因为temp是一个包含2个元素的数组,但您正在使用i05访问temp[i]

我明白你的意思。我想我是在考虑一个 char ** 的上下文,其中内存“块”对应于行……或类似的东西。我明白我的意思,但它在这里没有相关性。 - sherrellbc
假设我有一个 int temp[3][3],那么它在内存中不是这样结构化的吗:1 2 3 4 5 6 7 8 9?在这个连续的内存中,我们有符号“块”对应于二维数组的行。也就是说,1 2 3,4 5 6,7 8 9。因此,如果我们想要任意访问 temp [2] [2],我们必须执行:*(*(temp + 2 * 3)+ 2),对吗?原始帖子中包含的表达式中的第一个 col 是“列数”,而不是包含在 temp [row] [col] 中的 col。您包括的表达式:*(*(temp + row)+ col) 仅适用于 char * 数组。 - sherrellbc
Ajay在下面列出了C99标准的摘录,所以它必须是正确的 - 我只是不明白如何。二维数组是否使用指向行的常量指针分配?如果是这样,那么这比我之前想的要合理得多,因此上述表达式将是有效的。 - sherrellbc
编译器知道temp的元素是另一个数组,所以当它看到temp + row时,它知道要前进多少大小来找到下一个元素。 - Yu Hao

1
C99标准§6.5.2.1 ¶2规定-
引用块中写道:
下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))
因此,表达式temp[row][col]的求值结果为:
temp[row][col] --> *((temp[row]) + col) --> *(*(temp + row) + col)
|_______|  |
    |      |
   E1     E2

请注意区别,因此你的第一个程序没问题。

在第二个程序中,语句不是*(*(temp + row) + col),而是*(temp[row] + col)。这两种方式都是可以的。

char* temp[] = {a, b};

temp定义并初始化为包含2个字符指针的数组。但在for循环中,你以temp[i][h]的方式访问该数组,而i的范围是从04。访问超出数组边界的元素是未定义的行为。因此,你的第二个程序是错误的。


1
当你认为temp [0]等于atemp [1]等于b时,你猜对了。但是等等,为什么你要像尝试解除引用temp [2]一样继续下去呢?你没有正确初始化这个东西!
通过访问数组末尾之后的内存,您的程序会导致未定义行为。编译器并不总是有义务给您一个错误消息。请查看this问题。

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