来自Brian W. Kernighan的C编程语言
&运算符仅适用于内存中的对象:变量和数组元素.它不能应用于表达式,常量或寄存器变量.
如果不在内存中,表达式和常量存储在哪里?这句话是什么意思?
例如:
&(2 + 3)
为什么我们不能拿它的地址?它存放在哪里?
对于C++,答案是否也一样,因为C一直是它的父级?
这个链接的问题解释了这样的表达式是rvalue对象,并且所有rvalue对象都没有地址.
我的问题是这些表达式存储在哪里,以至于无法检索到它们的地址?
aaa*_*789 58
考虑以下功能:
unsigned sum_evens (unsigned number) {
number &= ~1; // ~1 = 0xfffffffe (32-bit CPU)
unsigned result = 0;
while (number) {
result += number;
number -= 2;
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
现在,让我们玩编译器游戏并尝试手动编译.我假设您正在使用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)
Run Code Online (Sandbox Code Playgroud)
现在,你可以看到,在代码中的常数(0,2,1)实际上显示为的CPU指令的一部分!事实上,1根本没有出现; 编译器(在这种情况下,只是我)已经计算~1并使用代码中的结果.
虽然你可以获取CPU指令的地址,但取一部分的地址通常是没有意义的(在x86中你有时可以,但在许多其他的CPU中你完全不能这样做),并且代码地址是从根本上不同于数据地址(这就是为什么你不能将函数指针(代码地址)视为常规指针(数据地址)).在某些CPU架构中,代码地址和数据地址完全不兼容(尽管大多数现代操作系统使用它的方式与x86不同).
请注意while (number)相当于while (number != 0).这0根本不会出现在已编译的代码中!它是由jnz指令暗示的(如果不是零则跳转).这就是为什么你不能拿出那个地址的另一个原因0- 它没有一个,它实际上无处可去.
我希望这能让你更清楚.
Use*_*ess 38
存储这些表达式的位置,以便无法检索地址?
你的问题不是很好.
这就像问为什么人们可以讨论名词的所有权而不是动词.名词指的事情,可能(潜在)所拥有,而动词指行动所执行的.你不能拥有一个动作或执行一件事.
表达式不存储在第一位,它们被评估.编译器可以在编译时对它们进行评估,也可以在运行时由处理器对它们进行评估.
考虑一下这句话
int a = 0;
Run Code Online (Sandbox Code Playgroud)
这样做有两件事:首先,它声明一个整数变量a.这被定义为您可以采取的地址.编译器可以在给定平台上执行任何有意义的操作,以允许您获取地址a.
其次,它将变量的值设置为零.这并不会与零值在编译的程序存在于某个地方的意思是一个整数.它通常可以实现为
xor eax,eax
Run Code Online (Sandbox Code Playgroud)
也就是说,XOR(异或)eax寄存器本身.这总是导致零,无论以前是什么.但是,0编译后的代码中没有固定的有价值对象来匹配0您在源代码中编写的整数文字.
顺便说一句,当我说a上面的内容是你可以采取的地址时 - 值得指出的是,除非你接受它,否则它可能没有真正的地址.例如,该eax示例中使用的寄存器没有地址.如果编译器可以证明程序仍然正确,那么它a可以在该寄存器中存活并且永远不会存在于主存储器中.相反,如果您在&a某处使用表达式,编译器将注意创建一些可存储空间来存储其a值.
请注意,我可以轻松地选择一种不同的语言,我可以使用表达式的地址.
它可能会被解释,因为一旦机器可执行输出替换它们,编译通常会丢弃这些结构.例如,Python具有运行时内省和code对象.
或者我可以从LISP开始并扩展它以提供对S表达式的某种操作地址.
它们两者的共同关键是它们不是C,这在设计和定义方面并不提供这些机制.
Lun*_*din 10
这些表达式最终成为机器代码的一部分.表达式2 + 3可能被转换为机器代码指令"将5加载到寄存器A".CPU寄存器没有地址.
将地址用于表达式并没有多大意义.你能做的最接近的是一个函数指针.表达式的存储方式与变量和对象的存储方式不同.
表达式存储在实际的机器代码中.当然,您可以找到评估表达式的地址,但这样做是没有意义的.
阅读有关装配的一些内容.表达式存储在文本段中,而变量存储在其他段中,例如数据或堆栈.
https://en.wikipedia.org/wiki/Data_segment
解释它的另一种方法是表达式是cpu指令,而变量是纯数据.
还有一件事要考虑:编译器经常优化掉东西.考虑以下代码:
int x=0;
while(x<10)
x+=1;
Run Code Online (Sandbox Code Playgroud)
此代码将被优化为:
int x=10;
Run Code Online (Sandbox Code Playgroud)
那么(x+=1)在这种情况下,地址意味着什么呢?它甚至不存在于机器代码中,因此根据定义它根本没有地址.
| 归档时间: |
|
| 查看次数: |
4435 次 |
| 最近记录: |