字面值的作用域是什么?编译器如何为其分配内存?

3
int x = 12;

12被称为整数字面量,因此无法在LValue中使用。

  1. 编译器如何分配内存给字面量?
  2. 字面量的作用域是什么?
  3. 为什么我们不能在其作用域内使用 &12 来获取其地址?

7
哎呀,12根本不是一个对象,x也不是。你可能想选择一个更好的例子,比如一个真正的对象。 - paxdiablo
3
这是一个整型字面量——请参考skjaidev的回答。 - paxdiablo
8
x 在 C++ 对象模型下指代一个对象。它由定义语句创建,具有存储期和类型。请参阅 C++11 标准中的第 1.8 节“C++ 对象模型”。在问题中,“对象”一词使用的就是这个意义,而不是面向对象编程中的“对象”的意思。 - bames53
4
相关引用为§1.8.1:“C++程序中的构造可以创建、销毁、引用、访问和操作对象。对象是存储区域。” - Seth Carnegie
3
“int”是一种数据类型,而不是一个类。在C语言中,没有“类”的概念;而在C++中,“类”类型是使用“struct”、“class”或者“union”声明的聚合类型。 - Mike Seymour
显示剩余8条评论
6个回答

4

好的,题目中举的是一个不好的例子。
但这个问题仍然有效:
让我们试试:

Foo getFoo() {return Foo();}

int func()
{
    getFoo().bar();   // Creates temporary.
    // before this comment it is also destroyed.
    // But it lives for the whole expression above
    // So you can call bar() on it.
}

int func2()
{
    Foo const& tmp = getFoo();  // Creates temporary.
                                // Does not die here as it is bound to a const reference.

    DO STUFF
}  // tmp goes out of scope and temporary object destroyed.
   // It lives to here because it is bound to a const reference.

编译器如何为临时对象分配内存?
由编译器决定,但很容易在堆栈帧上分配一点额外的内存并将其保留在那里。然后销毁它并减小堆栈帧的大小(尽管此答案对底层硬件做了很多假设,你永远不应该这样做(最好只把它看作是编译器施展着魔法))。
临时对象的范围是什么?
除非绑定到const引用,否则临时对象会一直存在,直到表达式结束(通常为;)。如果绑定到const引用,则会持续到该引用所属的作用域结束(有一些例外情况(如构造函数))。
为什么我们不能在其作用域内使用&12获取其地址?
因为12不是一个临时对象,它是一个整数字面值。

Loki,非常有帮助的回答,谢谢。@skjaidev和Loki,顺便问一下,编译器将12存储在目标文件中而不是RAM中是否有特殊原因? - Aquarius_Girl
1
@AnishaKaul:12是整型字面量,它可能没有被存储在任何地方(它是代码的一部分)。变量“x”是存储区域(一个对象),因此是可寻址的,无论这意味着RAM还是其他什么都是完全未定义的。你的问题在于你在考虑特定的硬件要求。标准故意避免硬件细节(比如RAM的概念)。变量“x”只有在使用它的地址时才需要具有物理地址;它可能只存在于寄存器中(但这是偏离标准未涉及的硬件细节)。 - Martin York
2
@AnishaKaul:int main() {12;}编译正常。因为12是一个字面量,所以是一个有效的表达式,也是一个有效的语句。请注意,它并没有做太多事情。字面量12可能被编码成目标文件中的汇编指令。而你代码中的变量x是一个存在于内存中的对象。 - Martin York
2
标准故意没有指定这样的内容(它试图非常硬件无关)。因此,所有这一切都取决于编译器。 - Martin York
这个答案是针对C++的。在C中,函数返回值从不是左值。 - Jens Gustedt
显示剩余4条评论

3
在你的例子中,12 是一个整数字面量。整数字面量几乎总是作为立即操作数“嵌入”在机器指令中。它们不存储在内存中,因此您无法通过地址访问它们,也没有范围。
int x = 12 

would translate to something like

movl 12 address_of_x_or_register

在几乎所有的架构中,12都是作为指令的一部分进行编码的。

为了明确起见,x仍然驻留在内存中(对于局部变量来说是堆栈,在全局变量的情况下是数据段),并且最终将包含值12。右手边的"对象"12是整数文字,它在指令执行之前或期间不驻留在内存中,而是"驻留"在指令本身中。


在编译过程中,编译器对它做了什么?存储? - Aquarius_Girl
1
编译器直接在机器指令中使用该值,不过这只适用于整数。例如,字符串常量会在数据段中分配存储空间。 - jman
是的。试一下,你可以使用 gcc -S foo.c 生成与你的 C 代码等效的机器码。然后查看生成的 foo.S 文件。 - jman
@AnishaKaul:不,你不能做出那样的假设。 - Martin York
2
只是为了明确,x仍然驻留在内存中(对于局部变量来说是堆栈,在全局段的情况下是数据段),并且在指令执行后包含值12。RHS值12在指令之前/期间不驻留在内存中,而是驻留在指令本身中。 - jman
显示剩余3条评论

2

1. 编译器如何为临时对象分配内存?

string myString = "hello";

在这种情况下,编译器调用转换构造函数来初始化一个新对象。

2. 临时对象的作用域是什么?

临时对象的生命周期只有到分号结束。

3. 为什么我们不能在其作用域中使用 &12 获取它的地址?(在这种情况下是 "hello")

&12 不是对象,它是传递给转换构造函数的参数。


"&12不是一个对象,它是传递给转换构造函数的参数"。为什么我们不能用&12获取12的地址呢? - Aquarius_Girl
什么是“转换构造函数”? - Aquarius_Girl
这个链接可能对理解“转换构造函数”有所帮助:http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr384.htm - Sanish
1
请查看这个 Stack Overflow 的问题,为什么你不能获取数字字面量的地址 https://dev59.com/IkjSa4cB1Zd3GeqPGIzt - Sanish
第二个链接展示了为什么你可以获取字面量的地址,而反过来则不行。:( - Aquarius_Girl

1
在编译期间,编译器会对它做什么?存储?
这个问题真的很难回答。编译器会尽可能地优化代码(可以在编译器工具中设置优化的数量或级别)。
例如,如果 x 可以通过汇编指令替换为 12,则 x 就会被“优化掉”,并且 12 将直接替换 x。
“volatile”关键字可用于欺骗编译器,在每次使用代码中的 x 时都检查它。这将生成更大的程序,但可能有助于为变量分配内存位置(存储)。

1

注意,其他答案大多澄清了你的误解,但这个没有:

字面量的作用域是什么?

这个问题没有意义,因为作用域是名称的属性(变量名、函数名、类型名、模板名、命名空间名等)。

字面量是一个数字的表示法,不是一个名称。


1

对象是“内存中的某物”。这意味着这个“某物”占用了进程地址空间的一部分(无论是在栈上还是在“空闲”内存中)。同时,这也意味着如果需要,您可以获取它的地址。

临时对象只是对象。编译器会生成代码来创建它们,以计算表达式所需的对象:

void f(const string&);

void g()
{
     f(string("Hello"));
}

调用f()的表达式将导致生成以下代码:

  • 创建临时字符串对象。构造函数以const char*作为参数调用;
  • 使用对该临时对象的引用作为参数调用f();
  • 销毁临时字符串对象。

这其中关键的部分是在表达式求值结束时销毁临时对象。临时对象仅存在于表达式本身中(也就是它们的临时作用域)。您可以尝试类似于:const string* ps = &string("Hello"),但您的编译器可能会发出警报,因为这样的表达式将导致创建指针,该指针引用已经不再被临时对象占用的内存。它可能仍然包含字符串对象成员,或者可能被程序中后续的表达式覆盖(更不用说临时对象的销毁将释放堆中由对象分配的内存)。使用ps将导致未定义的行为。

这里出现的另一个问题是像int x = 12这样的表达式中对象的性质。在这种情况下,x 可能是一个对象或者可能不是。这取决于编译器设置和代码跟随该行的情况。编译器可能决定将x放置在寄存器中。在这种情况下,x将不是一个对象,因为寄存器没有地址(至少在大多数平台上)。如果您不改变x的值,编译器甚至可以决定直接在指令中使用12。因此,“12”只存在于指令代码的一部分。
无论在以下代码中发生了什么,int x = 12都不会创建临时的12对象。如果编译器决定将12放入内存中,则指令将如下(伪代码):
store immediate address_of_x, 12

如果将x放在寄存器中,则会得到:

move immediate regX, 12

在这两种情况下,12将成为编译代码块指令的一部分。因此它不会是一个独立的对象。

“临时对象仅存在于表达式本身中(也就是它们的临时作用域)。” 临时对象是无名的,因此它们没有任何关联的作用域。 - curiousguy
@curiousguy,这就是我称之为临时作用域的原因。在表达式被评估时,临时对象存在。同样命名的对象存在于程序停留在其作用域时。 - Pavel Zhuravlev

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