【问题标题】:C++ difference in behavior between cout and printfcout 和 printf 之间的 C++ 行为差异
【发布时间】:2019-02-11 20:17:18
【问题描述】:

我有以下代码sn-p:

#include <iostream>
#include <stdio.h>
void foo(int**p)
{
    int y = 5; // memory for this is allocated, say at address 3000, and
               // that position of memory is filled with the value 5

    *p = &y;   //we equal the value (contents) at address 2000 to 3000;
}

int main(int argc, char **argv)
{
    int* p; 
    std::cout << &p << std::endl; 

    // p points to nowhere right now
    foo(&p); //we pass p's address to foo()

    printf("%d\n",*p); 
    std::cout << *p << std::endl;  
}

当我运行此代码时, printf 输出 5 但 std::cout 打印无效数据。谁能解释一下为什么?

【问题讨论】:

  • yfoo() 结束时超出范围,因此取消引用p 是未定义的行为。
  • 定义无效数据。你期待什么?
  • 我不确定 "we equal the value (contents) at address 2000 to 3000;" 是什么意思,但听起来你认为p 现在包含该值5?如果是这样,请再次查看 int**p 并注意它有 2 个星号,因此 *pint*(一个指针)。
  • 重要无关说明:// p points to nowhere right now 不正确。 p 指向某处,但由于它未初始化,您不一定知道它指向何处。您可能会看到它指向 null,因为堆栈在启动时被清零,但您不能指望这一点。
  • @Frank 不幸的是,如果除了上帝真的恨你之外没有其他原因,它可以指向一个有效的对象。剩下的就是争论语义。我坐在“指针会指向”阵营。

标签: c++


【解决方案1】:

我认为有必要深入了解您对 C++ 的基本误解:

// memory for this is allocated, say at address 3000, and
// that position of memory is filled with the value 5

这是“正确的”,因为这是大多数编译器解释代码的方式。但是,就 C++ 语言而言,这是一个实现细节

从语言的虚拟机角度来看,当您执行以下操作时:

*p = &y;

您是在说:“p 指向的 int 指针对象现在指向对象 y”。就这样。这是通过内存寻址完成的,这又是一个实现细节。

现在,当y 超出范围时,p 成为一个不指向有效对象的指针。取消引用这样的指针是未定义的行为。 顺便说一句,即使后来在同一内存位置分配了一个新的 int 对象,这仍然是正确的。

未定义行为的有趣之处在于它是编译器的全权委托。允许假设它永远不会发生,这往往会导致您的程序在发生时执行看似荒谬的指令。

我想我的意思是:除非您正在处理指向 charunsigned charstd::byte 的指针,否则从内存地址的角度考虑指针确实不是要走的路。

【讨论】:

  • 难道 OP 只是想写 **p = y;并将 5 分配给指定的值?提供此修复程序作为选项并回避所有 UB 可能是值得的。
  • @Jeffrey 那也是 UB,因为原始指针没有指向任何东西。
  • 呃,是的,我错过了原始指针也未初始化的事实
【解决方案2】:

是的,这绝对是指针问题,而不是 cout / printf 问题。

对于来自不直接使用指针的其他语言(例如 Java)的人来说,它们可能会带来很多麻烦。你真的需要完全理解它们才能使用它们。在这种情况下,您不仅在处理指针,而且还在处理指向指针的指针。

在您的具体示例中,方法 foo() 采用指向指针的指针。没关系,但是在这样做之前您确实需要很好地理解指针。

然后它为变量 y 分配(我相信在堆栈上)内存并填充该内存。然后将指针设置为指向 y。

当您的方法返回时,堆栈会重置,并且在堆栈上所做的任何事情都可能被丢弃。你不知道它会发生什么。但在这种情况下,您转身调用 printf(),这是另一个函数调用,它在堆栈上分配空间——与 foo() 所做的范围相同——并将新数据放在那里。您的指针现在指向该新数据。

这就是您要打印的内容。

现在,如果您真正想要的是让 foo() 填充值,那么您真正需要的是:

  1. P 应该是一个 int,而不是 int*,这会改变 main 中的大部分其他代码。

  2. Foo 应该采用 int*,而不是 int**。

  3. 最后一行应该是 *p = y,而不是 *p = &y。

换句话说,你传递给 foo 来存储你试图返回的值,而不是在哪里放置地址。

当然,大多数人都会让 foo 返回您的 int 值 :-) 但我假设您正在尝试更好地理解指针,这就是您这样做的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-08-06
    • 2018-08-12
    • 1970-01-01
    • 2014-07-25
    • 2020-03-14
    • 2011-11-04
    • 2017-10-09
    • 2011-01-04
    相关资源
    最近更新 更多