5

在下面有人可以解释执行时获得的输出:

#include<stdio.h>
void main()
{
char a[5][10]={"one","two","three","four","five"};
char **str=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)));
}

下面是观察到的输出:

0xbf7f6286 
0xbf7f6286 
0xbf7f6292 
0x38 
0x1 
  • &a[0] 是数组的起始地址的地址
  • &str[0] 相同

    有人能解释一下 str[3] 的地址是 0xbf7f6292 吗?根据我的理解, &str[0] 和 &str[3] 应该相差 30 个字节。

    还请有人解释其他两种情况的输出。

提前致谢

4

5 回答 5

8

非常简短的答案

改变这个:

char **str;

要这样:

char (*str)[10];

并在之后修复代码中的任何错误。的类型str与您的二维数组的类型不兼容。我冒昧地将返回类型固定为intfor main()(根据 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]);       // this is undefined behaviour
    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但为什么?要回答这个问题,必须了解类型指针算术的基础知识。


类型指针算术如何工作?

void在 C 和 C++ 中,除了类型之外的所有指针。具有与指针关联的基本数据类型。例如。

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不是一个字节;它是四个字节(我系统上的宽度)。接下来,用和作为一个字节来演示单个的宽度。最后,我们自定义的数据类型带有它的两个指针值,并显示它是 0x6C,即 108 字节宽。(由于结构填充和字段对齐,它可能会更大,但我们很幸运地看到了这个例子,事实并非如此)。这是有道理的,因为该结构包含两个 4 字节的数据字段(an和 a )和一个 100 个元素宽的 char 缓冲区。intcharpchrpchr+1Datadptrdptr+1intfloat

顺便说一句,反过来也是正确的,即使是有经验的 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放在 double-pointerstr中。

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

那是反向的“一”一词(反向是由我的系统上的多字节值的字节序处理引起的,但我希望问题很明显。那第二个值呢:

0x6f : o
0x77 : w
0x74 : t

对,就是“二”。那么为什么我们将数组中的字节作为指针呢?

嗯.. 是的,正如我在评论中所说。无论谁告诉您或向您暗示双指针和二维数组是同义词,都是不正确的。回想一下我们对类型指针算术的中断。记住这一点:

str[1]

和这个:

*(str+1)

是同义词。出色地。指针的类型是什么?str它指向的类型是一个char指针。因此,这之间的字节数差异:

str + 0

和这个

str + 1

将是 a 的大小char*。在我的系统上是 4 个字节(我有 32 位指针)。这就解释了为什么表观地址是我们原始数组基数的四个字节str[1]数据。

因此,回答您的第一个基本问题(是的,我们终于明白了)。

为什么str[3]0xbf7f6292

答:这个:

&str[3]

相当于:

(str + 3)

但是我们从上面知道这(str + 3)只是存储在 中的地址str,然后将类型str指向的宽度的 3 倍,即 a char *,以字节为单位添加到该地址。出色地。我们从你的第二个printf知道那个地址是什么:

0xbf7f6286

我们知道您系统上的指针宽度是 4 个字节(32 位指针)。所以...

0xbf7f6286 + (3 * 4)

或者....

0xbf7f6286 + 0x0C = 0xbf7f6292
于 2013-03-14T05:29:45.697 回答
3

前两个 printf 显然是打印相同的地址,即二维数组的第一个元素,它是一个一维数组。所以毫无疑问,它们都指向数组的开头,即a[0][0]。

char **str 是一个指针,因此当您执行 str[3] 时,它的内部意思是 (str+3) 并且很可能您的代码在 32 位机器上运行,因此它会根据指针 airthematic 提前跳到 12 个字节。不要与数组混淆,str 是二维指针而不是数组。

0xbf7f6286 str[0] 0xbf7f6292 str[3] diff 是 0xc,如预期的那样是正确的。

printf("\n%p", &str[1][56]); printf("\n%p", &( ( (str+4)+1)));

由于您对指针 airthematic 的错误理解,您试图打印最后两个声明。

我希望它有助于解决您的疑问。

于 2013-03-14T04:56:36.797 回答
1
char a[5][10]={"one","two","three","four","five"};
char **str=a;

让我解释一下两个起始指针的工作原理,以及它与数组的关系,以便您有更好的机会尝试自己解决问题。

传统上,双星指针分配有另一个指针的“地址”。

例子

char   a =  5;
char  *b = &a;
char **c = &b;

所以当你取消引用 c 一次

printf("\n%d",*c)

,你会得到 'b' 所包含的值,也就是 'a' 的地址。

当您取消引用 c 两次时

printf("\n%d",**c)

,你会直接得到'a'的值。

现在来谈谈数组和指针之间的关系。

数组的名称是其地址的同义词,这意味着数组的名称是其自身的位置。

例子:

int array [5] = {1,2,3,4,5};
printf("\n%p",array);
printf("\n%p",&array);
printf("\n%p",&array[0]);

他们都将打印相同的地址。

因此这是一个无效的指针分配

char **str=a;

因为 **str 需要一个持有地址的指针的地址,但您只是传递了一个地址[数组名称 = a]。

所以简而言之,一个两星指针,不应该用来处理一个二维数组。

这个问题涉及处理带有指针的二维数组。

于 2013-03-14T05:22:03.903 回答
0

当你说 str+3 时,它的意思是 str+(3*sizeof(char **))。因此,如果“char **”的大小为 4 字节,则 str 增加 12 字节。

于 2013-03-14T05:07:01.877 回答
0

如果你想打印一个二维数组,你应该使用两个循环(我建议两个'for'循环),并且一个嵌套在另一个里面。

至于输出,它们是(我不是说显而易见的)所有地址,因为当您在访问数组元素之前键入“&”时,您是在询问它的地址。

由于您将数组 a 设置为等于 str,因此实际上将指针 str 设置为数组“a”指向的地址。

当您使用访问运算符 [int n] 时,您实际上是在告诉您的计算机查看数组(或指针)实际指向的位置之外的“n”个地址槽。因此,为什么它比前两名多 16 个。

最后两个看起来像十六进制,我不完全确定最后两个输出行。

于 2013-03-14T05:07:35.150 回答