如果不在内存中,表达式和常量存储在哪里?

时间:2017-12-19 09:54:30

标签: c++ c

来自 C编程语言作者:Brian W. Kernighan

  

&安培; operator仅适用于内存中的对象:变量和数组   元素。它不能应用于表达式,常量或寄存器   变量

如果不在内存中,表达式和常量存储在哪里? 这句话是什么意思?

E.g:
&(2 + 3)

为什么我们不能取其地址?它存放在哪里?
对于C ++,答案是否相同,因为C一直是它的父级?

此链接question explains表示此类表达式为rvalue个对象,而所有rvalue个对象都没有地址。

我的问题是这些表达式存储在哪里,以至于无法检索到它们的地址?

6 个答案:

答案 0 :(得分:59)

考虑以下功能:

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)

现在,正如您所看到的,代码中的常量(021)实际上显示为CPU指令的一部分!事实上,1根本没有出现;编译器(在这种情况下,只是我)已经计算~1并在代码中使用结果。

虽然你可以获取CPU指令的地址,但取一部分的地址通常是没有意义的(在x86中你有时可以,但在许多其他的CPU中根本就不能这样做),并且代码地址与数据地址根本不同(这就是为什么不能将函数指针(代码地址)视为常规指针(数据地址))。在某些CPU架构中,代码地址和数据地址完全不兼容(尽管大多数现代操作系统使用它的方式与x86不同)。

请注意while (number)等同于while (number != 0)0根本不会出现在已编译的代码中!它由jnz指令暗示(如果不是零,则跳转)。这是为什么你不能取0的地址的另一个原因 - 它没有一个地址,它根本就没有。

我希望这能让你更清楚。

答案 1 :(得分:38)

  

这些表达式存储在哪里,以至于无法检索到地址?

你的问题不是很好。

  • 概念

    这就像问为什么人们可以讨论名词的所有权而不是动词。名词是指可能(可能)拥有的事物,动词引用已执行的动作。你不能拥有一个动作或执行任何事情。

  • 就语言规范而言

    表达式首先不是存储,它们是评估的。 它们可以在编译时由编译器进行评估,也可以在运行时由处理器进行评估。

  • 在语言实施方面

    考虑声明

    int a = 0;
    

    这有两件事:首先,它声明一个整数变量a。这是定义的是你可以采取的地址。编译器可以在给定平台上执行任何有意义的操作,允许使用a的地址。

    其次,它将变量的值设置为零。这 not 表示编译程序中某处存在值为零的整数。它通常可以实现为

    xor eax,eax
    

    也就是说,eax自身的XOR(异或)。这总是导致零,无论以前是什么。但是,编译后的代码中没有固定的值0来匹配您在源代码中编写的整数文本0

顺便说一句,当我说a以上是你可以采取的地址时 - 值得指出的是,它可能没有真正的地址,除非你接受它。例如,该示例中使用的eax寄存器没有地址。如果编译器可以证明程序仍然正确,那么a可以在该寄存器中存活,并且永远不会存在于主存中。相反,如果在某处使用表达式&a,编译器将注意创建一些可寻址的空间来存储a的值。

为了便于比较,我可以轻松选择其他语言,我可以获取表达式的地址。

它可能会被解释,因为一旦机器可执行输出替换它们,编译通常会丢弃这些结构。例如,Python具有运行时内省和code对象。

或者我可以从LISP开始并扩展它以提供对S表达式的某种操作地址。

他们两个共同的关键是他们不是C ,作为设计和定义的问题,不提供这些机制。

答案 2 :(得分:10)

这样的表达式最终成为机器代码的一部分。表达式2 + 3可能被转换为机器代码指令"将5加载到寄存器A"中。 CPU寄存器没有地址。

答案 3 :(得分:5)

将地址用于表达式并不合理。你能做的最接近的是一个函数指针。表达式的存储方式与变量和对象的存储方式不同。

表达式存储在实际的机器代码中。当然,你可以找到评估表达式的地址,但这样做是没有意义的。

阅读一下有关装配的内容。表达式存储在文本段中,而变量存储在其他段中,例如数据或堆栈。

https://en.wikipedia.org/wiki/Data_segment

解释它的另一种方法是表达式是cpu指令,而变量是纯数据。

还有一件事要考虑:编译器经常优化掉东西。请考虑以下代码:

int x=0;
while(x<10)
    x+=1;

此代码可能会优化为:

int x=10;

那么(x+=1)的地址在这种情况下意味着什么呢?它甚至不存在于机器代码中,因此根据定义它根本没有地址。

答案 4 :(得分:2)

  

如果表达式和常量不在内存中,则存储在哪里

在某些(实际上很多)情况下,常量表达式 存储。特别要考虑optimizing compilers,看看CppCon 2017:Matt Godbolt的talk “我的编译器最近为我做了什么?解开编译器的盖子“

在某些具有2 + 3的C代码的特定情况下,大多数优化编译器的constant folded为5,而5常量可能只是 in some {{ 3}} machine code的指令(作为一些位域),甚至没有明确定义的内存位置。如果该常量5是一个循环限制,一些编译器可能已经完成code segment,并且该常量将不再出现在二进制代码中。

另见loop unrolling答案等......

请注意this是用英语编写的规范。阅读其C11标准。另请阅读更大的C ++ 11(或更高版本)规范。

C(和C ++)的n1570禁止使用常量的地址。

答案 5 :(得分:-5)

答案太复杂了。您的初始语句的真正含义是它适用于堆栈内存或堆内存中的值,即您实际可以写入的内存。表达式和常量,即程序本身,实际上 存储在内存中,但您无法写入此内存。因此,能够参考那个记忆是没有意义的;如果你尝试,你会得到一个段错误。