强制 C++ 为参数分配新地址

Force C++ to assign new addresses to arguments

提问人:Noah Bishop 提问时间:12/17/2020 更新时间:12/17/2020 访问量:224

问:

似乎当我将不同的整数直接传递给函数时,C++ 会为它们分配相同的地址,而不是将不同的地址分配给不同的值。这是设计使然,还是可以关闭的优化?有关说明,请参阅下面的代码。

#include <iostream>

const int *funct(const int &x) { return &x; }

int main() {

  int a = 3, b = 4;
  // different addresses
  std::cout << funct(a) << std::endl;
  std::cout << funct(b) << std::endl;

  // same address
  std::cout << funct(3) << std::endl;
  std::cout << funct(4) << std::endl;
}

这个问题的更大背景是,我正在尝试构造一个指向整数的指针列表,我将逐个添加这些指针(类似于 )。由于我无法修改方法定义(类似于 的),我想存储每个参数的地址,但它们最终都具有相同的地址。funct(3)funct

C++ C++11 指针 引用 传递

评论

2赞 cigien 12/17/2020
你到底为什么要取整数的地址?并不是说你真的可以做到这一点,但它看起来像是你想要实现的目标。3
1赞 n. m. could be an AI 12/17/2020
C++ 不是这样工作的。值没有地址。变量可以。 是变量的地址,而不是传递给函数的任何值的地址。对函数的不同调用可能会导致也可能不会导致具有不同的地址。一旦函数返回,它就不复存在,其地址将失效,因此您无法存储该地址。&xxxx
0赞 user4581301 12/17/2020
这可能看起来正在工作(或不起作用),因为使用魔法允许引用来延长临时的寿命。由于变量是临时的,因此可以一遍又一遍地重用其位置。const
0赞 Mooing Duck 12/17/2020
我认为这实际上不是未定义的行为,因为他实际上并没有取消引用指针。这肯定是无用的,但不是未定义的行为。不会发生或需要临时延长寿命。
1赞 Eljay 12/17/2020
@user4581301 • 在 OP 的例子中,没有临时的寿命延长。

答:

5赞 Remy Lebeau 12/17/2020 #1

该函数接受绑定到变量的引用。const int *funct(const int &x)int

a并且是变量,因此可以绑定到它们,并且它们将具有不同的内存地址。bintx

由于该函数接受量引用,这意味着编译器也允许绑定到临时变量(而非常量引用不能绑定到临时变量)。xint

当您将数字文本传递给 时,例如 ,编译器会创建一个临时变量来保存文本值。该临时变量仅在进行函数调用的语句的生存期内有效,然后临时变量超出范围并被销毁。xfunct(3)int

因此,当您在单独的语句中进行多次调用时,编译器可以自由地为这些临时变量重用相同的内存,例如:funct()

// same address
std::cout << funct(3) << std::endl;
std::cout << funct(4) << std::endl;

实际上等同于:

// same address
int temp;
{
temp = 3;
std::cout << funct(temp) << std::endl;
}
{
temp = 4;
std::cout << funct(temp) << std::endl;
}

但是,如果在单个语句中多次调用,编译器将被迫创建单独的临时变量,例如:funct()

// different addresses
std::cout << funct(3) << std::endl << funct(4) << std::endl;

实际上等同于:

// different addresses
{
int temp1 = 3;
int temp2 = 4;
std::cout << funct(temp1) << std::endl << funct(temp2) << std::endl;
}

演示

评论

0赞 cigien 12/17/2020
您确定可以保证打印不同的地址吗?这听起来确实是对的。怎么样?cout << funct(3) << funct(4)cout << funct(3) << funct(3)
1赞 Remy Lebeau 12/17/2020
@cigien我不知道标准对此有何规定,但应始终打印不同的地址,因为这是在单个语句中,并且不能共享单个临时地址,因为它们一起位于同一范围内。临时对象一直保持在作用域中,直到创建它们的语句结束(即,直到终止),因此在这种情况下,必须是唯一的临时对象。现在,是创建 2 个临时的,还是共享 1 个临时的,我不知道。我怀疑这取决于编译器以及它的优化程度。funct(3) << funct(4)34;34funct(3) << funct(3)
0赞 cigien 12/17/2020
是的,这确实是有道理的,我想我可以弄清楚实现可能会对代码做什么,但我对标准所说的内容很感兴趣。我想我会发布一个语言律师问题,如果我找不到骗子的话。谢谢你的解释。
1赞 MSalters 12/17/2020
@RemyLebeau:临时对象(在现代C++中)有一些特殊的规则,但除非另有特别说明(参见 6.7.7 临时对象),否则将适用普通对象规则。2 个对象不能重叠,除非一个对象是另一个对象的子对象,或者两个对象都是同一对象的子对象。例外情况在这里不适用,因此这两个临时情况不能重叠。假设规则仍然适用,但打印地址使其可观察。
4赞 HAL9000 12/17/2020 #2

函数

const int *funct(const int &x) { return &x; }

将返回所引用的任何内容的地址。x

因此,正如您所期望的那样,这将打印以下地址:a

std::cout << funct(a) << std::endl;

表达式的问题在于,不可能引用常量并将其作为参数传递。常量没有地址,因此出于实际原因,C++ 不支持引用常量。C++ 实际支持的是创建一个临时对象,用值初始化它,并获取对象的引用。funct(3)3

基本上,在这种情况下,编译器将翻译以下内容:

std::cout << funct(3) << std::endl;

变成与此等效的东西:

{
  int tmp = 3;
  std::cout << funct(tmp) << std::endl;
}

除非您采取一些措施来延长临时对象的生存期,否则它将在函数调用后(或在下一个序列点之前,我不确定)超出范围。

由于在创建临时 from 之前,创建的临时内存超出了范围,因此第一个临时使用的内存可能会被重新用于第二个临时内存。34

评论

0赞 HAL9000 12/17/2020
看起来@Remy澄清了临时的确切寿命。
0赞 MSalters 12/17/2020
“同步点”?您可能正在考虑序列点,但这些序列点已不复存在。现代 C++ 使用“sequenced-before”,在这种情况下,临时对象的销毁在下一个语句之前排序。(当然,在返回后排序)funct
0赞 HAL9000 12/17/2020
@MSalters,澄清 C++ 的边缘情况总是好的。由于这不是问题的一部分,我没有查找确切的寿命,并故意让它们有点含糊不清。就我个人而言,无论如何,我都不会编写依赖于临时生命周期的代码。