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

Where are expressions and constants stored if not in memory?

本文关键字:存储 表达式 常数 在哪里 内存 如果不      更新时间:2023-10-16

来自 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)

现在,如您所见,代码(021)中的常数实际上是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 )的语义禁止使用常数。

的语义。