将存储分配给常量变量

6
我正在阅读一本提到以下内容的书:
如果编译器知道const的每个用途,它就不需要为其分配空间。例如:
1. const int c1 = 1; 2. const int c3 = my_f(3); 3. extern const int c4;
鉴于c3和c4的值在编译时不能确定,必须为c3和c4分配存储空间。
我不理解其中任何一个问题。我的疑惑是:
这里的持有是什么意思?它不仍然需要在内存中存储所有内容吗? 对于c1,我们没有任何存储分配吗?
请帮我澄清这些疑惑。
谢谢。
3个回答

5

c1与其他两个常量不同的地方在于它是用字面值初始化的。这使得编译器可以将该值放置在使用该常量的任何位置,例如:

int x = z + c1;

可以被替换为

int x = z + 1;

这意味着编译器不需要分配空间并将1存储在其中。 c3c4是不同的:一个是使用函数计算的,另一个来自不同的编译单元。这意味着编译器不能像处理c1那样进行替换:编译器不知道c3c4的值。因此,编译器会生成代码。
int x = z + c4;

就像c4是存储在内存中某个变量一样。由于在这种情况下c4是一个外部常量,链接器会解析它的位置,并填写编译器缺失的信息(即c4的地址),使程序完整并准备好运行。


感谢您的回答,但这是否意味着每当将变量定义为const并用文字初始化时,它就像宏一样运作,在程序中任何位置使用编译器都会用值替换变量? - bruceparker
2
@Leoheart 编译器允许进行这种替换,大多数编译器都会这样做。但据我所知,编译器不必“内联”已声明的常量:标准允许它们采用任一方式。此外,您可以获取已声明常量的地址,在这种情况下,编译器将其放置在内存中,并将其内联到使用它的地方。 - Sergey Kalinichenko
@dasblinkenlight - 除此之外,我也看到过编译器将定义存储在其符号表中,用于这样的常量变量。如果编译器实现了这一点,那么符号表将如何帮助它呢?就像你上面提到的那样:“int x = z + c1;”可以被替换为“int x = z + 1;” - Enkesh Gupta

2

Const有两个用途 - 替代宏(常量表达式)和不可变数据。

这个语句:

const int c1=1;

这实际上是一个类型安全的版本:

#define c1 1

这段代码:

int foo = c1;

可以简单地编译为:

int foo = 1;

请问哪个更有效率。

另一方面,这样做:

const int c3=my_f(3);

被用作不可变的c3。它很可能存在于内存中,但是您不能修改它。它本质上是 int c3=my_f(3); 的更安全的版本。

为了举例说明:

int a1[c1];
int a2[c3];

a1是有效的,因为编译器能够推断a1为常量表达式。a2不是有效的,因为尽管c3是const,但它可能在编译时无法确定。

C++11增加了constexpr关键字,它类似于const,但比const更严格。只有c1c2可以使用constexprc3也可以,但需要my_f也是constexpr


1
作为一个积分常量表达式,编译器有权使用常量折叠将其从程序中移除。只有在取它的地址时才不成立。此外,现代优化编译器可以使用LTO、内联和常量折叠来处理c3和c4,如果可能的话。
如果您不取变量的地址,编译器没有义务分配它,如果它可以生成具有等效结果的代码,则根据as-if规则。
编辑:常量折叠是指编译器在编译时评估表达式而不是运行时。例如,您可以合法地执行int x [3 + 4];,其中3 + 4在编译时计算。一些特定的例子,尤其是涉及ICE的例子,是标准规定的,但实现可以进行更多的操作。LTO是链接时间优化,即当它们链接在一起时,编译器在翻译单元之间执行优化。

这意味着编译器可以内联my_f的内容,然后(取决于内容)将其常量折叠出来,使c3成为一个常量表达式,然后将其常量折叠到使用它的任何地方而不分配它。对于c4,LTO可能会将c4的值作为常量表达式产生,这样它也可以被常量折叠和删除。

在C++11中有constexpr函数,允许在此区域中完成更多工作。


很抱歉,我也无法理解你的回答。常量折叠和LTO是什么? - bruceparker
最后一个问题:这是否意味着每当一个变量被定义为const并用字面值初始化时,它就像一个宏一样,在程序中使用的任何地方编译器都会用值替换变量? - bruceparker
@Leoheart:不是的,完全不一样。宏是文本替换,常量折叠是语义替换。其次,编译器只能对某些情况和某些类型进行这种替换。 - Puppy

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