指向二维字符数组的指针和指向二维整数数组的指针有什么区别?

4
    #include <stdio.h>

    int main()
    {
        char str[3][15] = {"Pointer to","char","program"};
        char (*pt)[15] = str; // statement A
        char *p = (char *)str; // statement B
        printf("%s\n",p[3]); // statement C  - Seg Fault in this line
        printf("%s\n",p); // working properly displaying "Pointer to"
        printf("%s\n",p+1); // here it is pointing to second element of first array so displaying "ointer to"
        printf("%s\n",pt+1); // printing properly "char" as expected
        int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
        int (*nm)[3] = num[1];
        int *n = num;
        printf("n - %d\n",n[10]); // statement D
        printf("nm - %d\n",nm[0][0]);
        return 0;
    }

我的问题:

  1. 请帮我清楚地了解char数组和int数组的数据存储机制。

  2. 在上面的程序中,当char数组指针指向2D char数组(如A语句所示)时,它能够正确显示,但是当由普通char指针指向并尝试在C语句中打印char时,会出现SegFault错误。实际上,它应该打印'n'(第一个数组“Pointer to”中的第三个字符),因此我对为什么在int数组中我可以在D语句中得到正确的元素n = 11而在这种情况下(C语句)无法正确打印感到困惑。

  3. char数组的数据将如何存储?将以以下形式存储吗:


char str[3][15] = {{'P','o','i','n','t','e','r',' ','t','o'},
                   {'c','h','a','r'},
                   {'p','r','o','g','r','a','m'}};

如果它以这种方式存储,那么它应该像语句D中显示的整数指针数组一样工作。请帮助我指导这个问题,并澄清我在char和int数组存储方面所遇到的问题。

char str[3][15] = {{'P','o','i','n','t','e','r',' ','t','o','\0'}, {'c','h','a','r', '\0'}, {'p','r','o','g','r','a','m', '\0'}}; - LPs
语句 D 能够正常工作是因为 num 被分配为单个数组,编译器使用以下公式访问它:给定语句 num[row][col],则相当于 *((int *)num + row * 4 + col) - miravalls
printf("%s\n",p[3]); --> printf("%s\n",&p[3]); - LPs
语句B与p = str [0]相同,因为数组如何在堆栈上声明。 p [3]返回一个字符,而不是@LPs所指出的地址。 如果您想打印p [3],则格式说明符应为%c - miravalls
2个回答

4
让我们逐步查看您的代码。
char str[3][15] = {"Pointer to","char","program"};

您创建了一个包含三个长度为15的char数组的数组。您正在使用字符串字面量初始化每个char数组。如果字面量比数组短,则最后几个元素将填充为零,因此与下面这行代码相同:

char str[3][15] = {
    {'P', 'o', 'i', 'n', 't', 'e', 'r', ' ', 't', 'o', 0, 0, 0, 0, 0},
    {'c', 'h', 'a', 'r', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {'p', 'r', 'o', 'g', 'r', 'a', 'm', 0, 0, 0, 0, 0, 0, 0, 0}
};

那么

char (*pt)[15] = str; // statement A

在这里,您创建了一个指向包含15个字符的数组的指针pt,并使用str[0]的地址进行初始化,即pt指向str中的第一个char[15]数组。

接下来,

char *p = (char *)str; // statement B

不行。据我所见,你试图使p指向str占用的内存中的第一个char。表达式str具有char(*)[15]类型,即它是字符数组的指针,而不是指向char的指针(因此你被迫使用强制转换),并且无论str实际上指向存储'P'的单元格的事实如何,你都应该以更加类型安全的方式进行操作:

char *p = &str[0][0]; // the same as "p = str[0];"
str[0]指的是str的第一个元素,即str[0]的类型是由十五个char组成的数组,然后你可以只引用第一个char并获取它的地址 - &(str[0])[0],或者直接使用类型为“数组”的表达式会变为“指向第一个数组元素的指针”的事实,这就是为什么str[0]也能工作的原因。
让我们继续吧。
printf("%s\n",p[3]); // statement C  - Seg Fault in this line

这行代码会产生未定义的行为,因为格式说明符要求第二个参数为const char *,但是您传递了char。如果您想打印一个字符,请使用以下代码:

printf("%c\n", p[3]); // prints "n"

那么

    printf("%s\n",p); // working properly displaying "Pointer to"
    printf("%s\n",p+1); // here it is pointing to second element of first array so displaying "ointer to"

这些代码很有效,因为第二个参数的类型是正确的,我们知道字符串以空字符结尾。

printf("%s\n",pt+1); // printing properly "char" as expected

坦率地说,这是不正确的,因为pt + 1是“指向字符数组的指针”,但你应该传递“指向字符的指针”。应该改写为:
printf("%s\n",*(pt+1)); // or pt[1]

但是它似乎有效,因为尽管存在类型不兼容,两个指针指向相同的位置。

下一节关于 int

int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*nm)[3] = num[1];
int *n = num;

以下是两个错误:nm不应该用num [1]初始化,因为它们具有不兼容的类型:"指向三个int数组" vs. "四个int数组/ int指针"(通过衰减)。而且n也不能用num初始化,因为它们的类型也不兼容。根据我对您要求的猜测,它应该是这样子的格式:
int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*nm)[4] = num + 1;
int *n = &num[0][0];

还有最后一些:

printf("n - %d\n",n[10]); // statement D
printf("nm - %d\n",nm[0][0]);

解除引用是正确的,传递的参数也正确,但请记住指针初始化不正确。

希望我已经回答了你所有的问题。


太棒了。比我的好。;) - LPs

2

你的段错误是由于向printf函数传递了错误的类型。

使用p[3],你在解引用指向矩阵str第一行的第四个char的指针。与*(p+3)相同。

如果要打印第三个字符,应该

printf("%c\n",p[3]);

如果你想打印第一个C-String(矩阵的第0行),你需要执行以下操作:
printf("%s\n",&p[3]);

由于 %s 需要一个 char *,因此:

如果你在命令中加入 -Wall 选项(至少对于gcc),编译器会向你显示一个有用的警告:

test.c:8:9: warning: format ‘%s’ expects argument of typechar *’, but argument 2 has typeint’ [-Wformat=]
         printf("%s\n",p[3]); // statement C  - Seg Fault in this line

关于第三个问题,您必须注意正确的存储方式是:

char str[3][15] = {{'P','o','i','n','t','e','r',' ','t','o','\0'},
                   {'c','h','a','r','\0'},
                   {'p','r','o','g','r','a','m','\0'}};

由于C-String是以null结尾的,因此例如字符串"Pointer to"将占用11个字符。
最后一件事是关于int指针。它能够正常工作是因为%d格式说明符需要一个int值,而不是一个地址。因此,写成:
printf("n - %d\n",n[10]);

这是完全正确的,因为n[10]解引用了num矩阵的第11个元素,也就是第3行的第3个元素。与*(n+10)相同。


使用 p[3] 进行解引用操作时,您将访问指向第三个字符的指针。这是不正确的。 - 2501
使用 p 指向内部数组的边界之外,例如 p[10] 是未定义的行为。 - 2501
@2501 第一条评论已经编辑过了,但是第二条评论我还不明白你的意思。 - LPs
抱歉,我指的是指针n。它的初始化是错误的,但如果我们忽略这一点,n指向的是类型为int [4]num[0]。访问n[10]超出了边界。 - 2501
@2501 我猜到了。也许是最近在海边晒太阳把我的脑子烤糊了,但我真的不明白你的意思。num 是一个 3x4 的矩阵。内存是连续的,所以从 &num[0][0] 开始有 12 个 int。因此 n[10] 是合法的。我需要喝咖啡吗? ;) - LPs
显示剩余3条评论

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