如果不在内存中,则在哪里存储表达式和常数
Where are expressions and constants stored if not in memory?
来自 c编程语言 Brian W. Kernighan
&操作员仅适用于内存中的对象:变量和数组 元素。它不能应用于表达式,常数或注册 变量。
如果不在内存中,则在哪里存储表达式和常数?那报价是什么意思?
例如:
&(2 + 3)
我们为什么不能接受它的地址?它存储在哪里?
C 的答案也会相同,因为C是其父母?
这个链接的问题解释说,这种表达式是rvalue
对象,所有rvalue
对象都没有地址。
我的问题是这些表达式存储在哪里,以至于无法检索它们的地址?
考虑以下功能:
unsigned sum_evens (unsigned number) {
number &= ~1; // ~1 = 0xfffffffe (32-bit CPU)
unsigned result = 0;
while (number) {
result += number;
number -= 2;
}
return result;
}
现在,让我们玩编译器游戏,然后尝试手工编译。我将假设您正在使用X86,因为这是大多数台式计算机使用的。(x86是Intel兼容CPU的指令集。)
让我们浏览一下此例程的简单(不优化)版本时的编译时的样子:
sum_evens:
and edi, 0xfffffffe ;edi is where the first argument goes
xor eax, eax ;set register eax to 0
cmp edi, 0 ;compare number to 0
jz .done ;if edi = 0, jump to .done
.loop:
add eax, edi ;eax = eax + edi
sub edi, 2 ;edi = edi - 2
jnz .loop ;if edi != 0, go back to .loop
.done:
ret ;return (value in eax is returned to caller)
现在,如您所见,代码(0
,2
,1
)中的常数实际上是CPU指令的一部分!实际上,1
根本没有出现;编译器(在这种情况下,只有我)已经计算~1
并在代码中使用结果。
虽然您可以获取CPU指令的地址,但毫无意义地获取其中一部分的地址(在x86中,您有时可以,但是在许多其他CPU中,您根本无法做到这一点),代码地址与数据地址根本不同(这就是为什么您不能将功能指针(代码地址)视为常规指针(数据地址)的原因。在某些CPU体系结构中,代码地址和数据地址完全不相容(尽管在大多数现代OS使用的方式上不是X86的情况下)。
确实注意到while (number)
等于while (number != 0)
。0
根本不会显示在编译的代码中!jnz
指令暗示它(如果不是零,则跳跃)。这就是您无法接受该0
地址的另一个原因 - 它没有一个,实际上是没有任何地方。
我希望这对您来说更清楚。
这些表达式存储在哪里,以至于无法检索地址?
您的问题不是很好。
-
概念上
这就像问为什么人们可以讨论名词的所有权而不是动词。名词是指可能(可能)拥有的事物,动词是指执行的 action 。您不能拥有动作或执行任何操作。
-
在语言规范方面
表达式不是首先存储的,它们是评估的。它们可以在编译时通过编译器对它们进行评估,也可以在运行时由处理器评估。
-
在语言实施方面
考虑语句
int a = 0;
这做了两件事:首先,它声明了整数变量
a
。这是定义的是您可以接受的地址。由编译器决定在给定平台上做任何有意义的事情,允许您要接收a
的地址。其次,它将该变量的值设置为零。这确实是不是是指带有值零的整数存在于您编译的程序中的某个地方。它通常可以用作
xor eax,eax
也就是说,XOR(独家 - 或)
eax
与自身登记。这总是导致零,以前有任何东西。但是,编译代码中没有固定的值0
的对象,可以匹配您在源中写的整数0
。
顺便说一句,当我说上面的a
是您可以接受的地址时 - 值得指出的是,除非您接受,否则它可能没有地址。例如,该示例中使用的eax
寄存器没有地址。如果编译器可以证明程序仍然正确,则a
可以在该寄存器中过着一生,而在主内存中永远不存在。相反,如果您在某处使用表达式&a
,则编译器将注意创建一些可寻址的空间以存储a
的值。
请注意,我可以轻松地选择i can 的其他语言。
它可能会被解释,因为汇编通常一旦替代机器的输出就会丢弃这些结构。例如,Python具有运行时内省和code
对象。
,或者我可以从LISP开始并将其扩展以提供S-Expressions的某种地址操作。
他们俩共同的关键是它们不是C ,在设计和定义方面,它们不提供这些机制。
这样的表达式最终是机器代码的一部分。表达式2 + 3
可能会转换为机器代码指令"加载5到寄存器A"。CPU寄存器没有地址。
将地址带到表达式上并没有真正的意义。您可以做的最接近的是功能指针。表达式不是与变量和对象相同的含义。
表达式存储在实际的机器代码中。当然,您可以找到评估表达式的地址,但是这样做是没有意义的。
阅读一些有关汇编的内容。表达式存储在文本段中,而变量存储在其他段中,例如数据或堆栈。
https://en.wikipedia.org/wiki/data_segment
解释它的另一种方法是表达是CPU指令,而变量是纯数据。
还有一件事要考虑:编译器通常会优化远离事物。考虑此代码:
int x=0;
while(x<10)
x+=1;
此代码可能会优化为:
int x=10;
那么,在这种情况下,(x+=1)
的地址意味着什么?它甚至都不存在于机器代码中,因此 - 根据定义 - 根本没有地址。
如果不在存储器中,则存储在哪里表达式和常数
在某些(实际上很多)情况下,常数表达式是不是存储的。特别是考虑优化编译器,并参见CPPCON 2017:Matt Godbolt的Talk "我的编译器最近为我做了什么?拆开编译器的盖子"
在您具有2 + 3
的某些C代码的特定情况下,大多数优化的编译器都将其持续折叠为5,而5个常数可能只是 Inside 某些机器代码指令(作为Bitfield)您的代码段,甚至没有定义明确的内存位置。如果该常数5是一个循环限制,则某些编译器可能会进行循环展开,并且该常数在二进制代码中不会出现。
另请参阅此答案等...
请注意,C11是用英语编写的规范。阅读其N1570标准。另请阅读C 11(或更高版本)的更大规范。
c的语义(和C )的语义禁止使用常数。
- 将字符串存储在c++中的稳定内存中
- std::原子加载和存储都需要吗
- (C++)分析树以计算返回错误值的简单算术表达式
- C++:将控制台输出存储在宏中更好吗
- 在VS2010-VS2015下编译时,如何使用decltype作为较大类型表达式的LHS
- 提升精神:解析布尔表达式并简化为规范范式
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 使用正则表达式regex_search在字符串中查找字符串
- 使用QProcess执行命令,并将结果存储在QStringList中
- 如何确认我的constexpr表达式实际上已经在编译时执行
- 如果不在内存中,则在哪里存储表达式和常数
- C++ 时间存储time_point表达式必须是可修改的左值
- Python -> C++ 习语:将 lambda 表达式存储在映射/容器中
- 如何将正则表达式模式存储为正则表达式对象或字符串
- 存储表达式的最佳实践
- 如何在C++11中将lambda表达式存储为类的字段
- 存储前缀表达式(递归)
- 将正则表达式存储在变量(std::string)中
- ' const int a = 1; '是' a '一个常量表达式,如果' a '具有自动存储持续时间
- 存储在vector中的表达式的延迟求值