将指针显式转换为“指向指针的指针”

7

我在一次面试中看到了这段代码。

int main()
{
    int **p;
    p = (int **) new int(7);
    cout<<*p; 
    return 0;
}

我本来期望在*p处会有一些运行时错误。但是当我运行代码时,它成功执行并输出了“0x7”。请问有人能解释一下这是如何工作的吗?谢谢。


1
你想要做什么?一个基本的经验法则是,如果你需要进行类型转换,那么你的方法就不正确。由于 new int(7) 返回 int* 类型,所以你需要进行类型转换。但是进行类型转换并不能使代码变得正确,它只是掩盖了你犯错的事实。请移除类型转换并从此处开始工作。 - David Heffernan
@David:代码可能来自面试,而不是原始帖子。 - Vlad
@ David Heffernan:这是笔试中的一道面试题。有四个选项:编译时错误,运行时错误,代码将正常运行和以上都不是。我甚至不明白他们想通过这样的面试问题来衡量什么样的知识。 - Forever Learner
@Mr Lister:很可能,即使sizeof(int)!= sizeof(pointer),这也不会失败。它只会覆盖一些内存,但很有可能这不会导致程序崩溃。 - Vlad
好的,它将从未分配的内存中读取。最好的情况是它打印一些随机数。只要我们同意根据情况可能出现“运行时错误”或“正常运行”,那就没问题。 - Mr Lister
显示剩余2条评论
5个回答

5
正确的答案应该是“以上都不是”,除非你有一些额外的限制条件。基本上,这段代码是分配了一个整型,并将那段内存解释成一个整型指针(通过 reinterpret_cast)。第一个问题是,由于这是一个 reinterpret_cast,在一般情况下,结果是未指定的;如果 int 的大小小于 int* 的大小(比如在64位架构中),结果就会变成未定义行为,因为你读取超过了 new 调用中分配的大小。

当您将 int(32 位)转换为 int*(64 位)时,结果会扩展到 64 位。 - gulyan
@gulyan 但在这种情况下,编译器不知道它应该执行那种类型的强制转换。唯一进行的转换是从int *到int **,在上面的行中! - Mr Lister
1
@gulyan:不,在从32位整数到64位整数的static_cast中,编译器将执行扩展大小的转换,但在reinterpret_cast中,它将只添加代码来重新解释内存。也就是说,它将获取指向int的指针,并读取下一个64位(8字节)作为int *,这是未定义的行为。这是C样式转换最大的问题之一,它不像显式的C++转换那样清晰。 - David Rodríguez - dribeas

4
你创建一个新的int变量,并将其初始化为值7。
int *x = new int(7);

你可以将它转换为指针(例如内存地址7或0x07)。
int **p = (int**) new int(7);

然后你可以用cout显示这个地址。
*p is equal to (int*)7

这是一个值为7的指针。


2
这似乎表明代码是正确的,或者至少在任何情况下都会产生相同的输出,但实际上并不是这样。对 p 的解引用会导致未定义的行为。 - David Rodríguez - dribeas

0
new int(7);

分配内存给一个值为7int,并返回指向它的指针。

int **p = (int **) new int(7);

告诉编译器将该内存解释为一个 int**

cout << *p;

告诉编译器,在地址*p处是一个int *类型的值,并输出其值。该值为0x07(它将int 7视为地址)。多次解引用会导致崩溃(确切地说是未定义的行为)。


3
现有的代码已经存在未定义的行为......不需要进行额外的解引用操作。 - David Rodríguez - dribeas
@Luchian Grigore:谢谢,这很有帮助。 - Forever Learner

0
int main()
{
    int **p;                  // declare pointer to pointer called p
    p = (int **) new int(7);  // new allocates integer initialized to value of 7 and returns a pointer.  Cast the pointer to a point to pointer.  p now represents a pointer to a pointer with contents of 0x7.  If you called **p you would get the contents at address 0x7.
    cout << *p; // dereference p, this yields a pointer, which is an int of value 0x7.
}

这个问题很可能是为了测试你对指针的了解,但似乎并不是非常实际。

0
int main()
{
    int **p = (int **) new int(7);
    cout << *p;
}

所以,new int(7) 分配了 sizeof(int) 字节的内存并返回一个指向它的指针。假设内存恰好位于地址 X 处。然后将 X 强制转换为 (int**) 并存储在 p 中。

*p 解引用 int**,这意味着它将地址为 Xint 值 7 解释为 int*

  • 如果 sizeof(int*) 大于 sizeof(int),那么作为 int* 读取将超出使用 new 分配的缓冲区 - 不管数据类型和任何重新解释都是未定义的行为。

  • 如果它们大小相同,则 CPU 将尝试将包含整数 7 的内存读取为 int* - 这通常会产生值 int*(7),但查看标准 5.2.10.5:

整数类型或枚举类型的值可以显式转换为指针。[脚注:将具有零值的整数常量表达式(expr.const)转换为指针(conv.ptr)始终产生空指针,但将其他恰好具有零值的表达式转换为指针不一定会产生空指针。 --- 结束脚注] 将指针转换为足够大小的整数(如果实现中存在这样的整数),然后再次转换为相同的指针类型,将具有其原始值;指针和整数之间的映射是另外由实现定义的。
因此,整数值保证可以转换为某个指针值-没有任何未定义的情况-但该值是实现定义的。 仍然,作为可逆操作,int 7几乎肯定会产生一个值为7的int*,从而解释了输出“7”的观察结果。
如果指针大小小于int大小,则它将读取int值的一片,并将其解释为指针。 根据字节序,该片可能为0或7或甚至其他内容,但是作为一个int,它必须能够转换为将显示的某些指针值。

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