【问题标题】:Can anyone please explain to me the following任何人都可以向我解释以下内容
【发布时间】:2018-05-04 18:33:32
【问题描述】:

谁能向我解释一下在以下 sn-p 运行期间会发生什么以及为什么会打印 33? 谢谢,

#include <stdio.h>

void main(){
    int* p = (int*)17;
    printf("%d\n", (int)(long)(p+4));
}

【问题讨论】:

  • 据我所知,关于这段代码的一切都是未定义的行为。它可以打印任何东西
  • @tadman 不是真的...
  • 好吧,只有p+4 部分。
  • 将整数转换为指针是“实现定义的”,指针上的算术也是如此,系统可能在该固定地址有一个隐藏设备:)

标签: c pointers casting


【解决方案1】:

p 是指向 int 或某个 ints 的指针。

您将其初始化为指向地址 17。(这是一个大问题,有几个原因,我们会谈到。)

然后您将 4 添加到它。显然,您机器上的sizeof(int) 是4,也就是说,int 占用4 个字节(32 位)。当您将 4 添加到 int 指针时,编译器知道您想让它指向 4 int 的值更远,因此编译器将 4 × 4 = 16 添加到地址。现在p 指向地址 33。

然后你“转换”(转换)p 从指针到long。所以现在,不是指向地址 33 的指针,而是数字 33。

然后你再次施放它,从longint。这基本上将其从 33 转换为 33。(如果您机器上的类型 long 大于 4 个字节,则此转换可能涉及从 64 位值 33 转换为 32 位值 33,即,从0x00000000000000210x00000021。)

然后使用%d 打印最后一个int 值。所以你看到了数字 33。

现在,通常是这样说的

int* p = (int*)17;

是个坏主意,因为您有一个指针,其值可能无法使用。通常,您对指针所做的一件事是操作它们指向的值。但是如果你说

printf("value pointed to by p is %d\n", *p);

您最终会尝试从内存中的地址 17 获取 int 值。但是 (a) 您可能没有从地址 17 读取的权限,并且 (b) 17 不是 4 的倍数,因此您的处理器可能甚至不愿意尝试从那里获取 int,即使您确实得到了许可。所以这段代码几乎肯定会崩溃。

但是由于在您的代码中,您实际上从未尝试对 p 假设指向的 int 执行任何操作(在您添加 4 之前或之后都没有),您的代码可能 - 并且几乎没有 - - 似乎“工作”。

一方面,这是糟糕的、不可移植的、几乎没有定义的代码,如果不是完全未定义的话。但是话又说回来,它可能并不实用(显然没有人会运行它来完成任何实际工作)。因此,如果我们从中学到的只是指针运算的工作原理(特别是编译器如何根据指向对象的大小自动缩放),也许我们不必花费太多时间痛斥它的许多丑陋和不完美之处。 (关于代码是 undefined 还是仅仅是 implementation-defined,评论线程中有一些小问题,但只要你注意不要编写代码像这样真的,你不必担心区别。)

如果您想学习或多或少相同的关于指针运算的课程,但使用更合理且大多数可移植的程序,试试这个:

#include <stdio.h>

int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int main()
{
    int *p = &a[3];
    printf("p is %p and points to %d\n", p, *p);
    printf("p+4 is %p and points to %d\n", p+4, *(p+4));
}

在这里,我们没有将 17 之类的人工值填充到 p 中,而是将其初始化为指向一个实际的数组,正如指针所指的那样。而不是将指针值转换为int 并使用%d 打印它,而是使用%p 打印它,它旨在打印指针。

在我的机器上,这个程序打印

p is 0x10f47a02c and points to 3
p+4 is 0x10f47a03c and points to 7

如您所见,指针值不是像 17 和 33 这样容易理解的小数字,而我的机器选择以十六进制打印它们。尽管如此,很容易验证0x10f47a03c - 0x10f47a02c0x10,或16。我们添加了4,意思是“让它指向现在指向的第4 个int”,编译器添加了16。

[脚注。我说这是“主要是便携式的”。为了使其完美便携,您必须将 printf 调用更改为

    printf("p is %p and points to %d\n", (void *)p, *p);
    printf("p+4 is %p and points to %d\n", (void *)(p+4), *(p+4));

严格来说,%p 仅定义为打印指向void 的通用指针,而不是任意其他指针类型。所以,严格来说,我们需要那些 (void *) 强制转换,将指针值转换为正确的指针类型以便打印。]

【讨论】:

  • @FelixPalmen: C 2011 6.5.6 8 用指针定义加法。它指定了某些情况下的结果,这里都不适用,然后说“……否则,行为是未定义的。”
  • @FelixPalmen:指针操作数和结果并不都指向同一个数组的元素或超过它的元素。因此,条件不满足,所以“否则”适用。了解 C 中指针的背景可能有助于解释本段。 C 支持多种地址空间,而不仅仅是平面地址空间。在其中一些中,oointer 算术必须在分配的内存段的上下文中完成。实现指针算法需要的不仅仅是按大小缩放和加法。所以 C 标准使指针算术不在数组中未定义。
  • @FelixPalmen 仅在同一数组中的指针上明确定义指针运算的原因是承认两个不同的数组可能位于两个不同的内存段中的可能性。我称之为支持非平面地址空间!如果pq 指向不同的数组,计算p-q 与询问“主街123 号和第一大道456 号之间有多少房屋?”一样有意义。 (答案可能不是如乌鸦般的距离或毕达哥拉斯距离,更不用说“曼哈顿距离”:-)。)
  • 将整数转换为指针并返回是“实现定义的”。如果平台确定它在该地址有一个足够大的对象,那么算术p+4 也是有效的。系统可能在那个固定地址有一个隐藏设备:) 简而言之,这个程序是不可移植的。
  • @EugeneSh。如果p 是指向对象的有效指针并且该对象至少具有sizeof(int[3]) 字节,则定义该算术。如果系统在“地址”17 有一个足够大的对象,这很好。观察这些断言中术语 if 的多次使用。
【解决方案2】:

好吧,33 的结果告诉我你的机器使用 intsizeof(int) == 4(4 个字节)。

允许将整数转换为指针,结果由实现定义。这就是这条线的作用:

int* p = (int*)17;

现在它是一个指向 int 的指针,p+4 将把 int 的大小增加到指针的 4 倍。 17 + 4*433

你的第二行

printf("%d\n", (int)(long)(p+4));

将此指针转换为long,然后立即将其转换为int。转换为long 在这里没有任何作用。

所有这一切的结果是定义的实现,因为您在其他机器上得到不同的结果。无法保证 17 是指针的有效值,因此转换可能会为您提供不同的值。

但假设17 是一个有效的指针值,并且您机器上的int 的大小为4,则结果为33

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多