5

我知道在 C 中,如果一个变量是用register关键字显式指定的,那么就不能&在它上面使用运算符,这对我来说很有意义,因为没有变量的“地址”这样的东西总是保存在寄存器中。

我的问题是,如果编译器自己决定将变量存储在寄存器中而不是溢出它,那么&在代码执行期间操作符会发生什么?

我可以想到编译器可能会尝试处理的两种方法:

  1. 尝试模仿这种&行为,但这似乎很麻烦,我不知道如何严格有效地做到这一点。
  2. &如果运算符与此变量一起使用,则始终溢出变量。

在这种情况下,C 是采用其中一种方法还是做其他事情?

4

4 回答 4

5

变量是否在硬件寄存器中实现的事实必须对用户完全透明。由编译器来实现这一点,以便始终可以通过指针访问当前值。

当用户决定使用关键字声明变量时,情况就会改变register。然后&简单地禁止使用操作员。

于 2020-07-06T12:57:46.563 回答
2

我不是编译器专家,但我的印象是它有点相反。首先,编译器尝试优化代码。如果优化后不需要变量的地址,则将其放入寄存器中(或者可能优化完全不存在)。

当然,如果&运算符根本没有应用于变量,那么它肯定是一个候选者。但是即使&x源代码中确实出现了,优化后对地址的需要也可能消失。

作为一个简单的例子,如果我们有

int x = 7;
foo(*&x);

编译器可以看到它*&x与 完全等价x,因此可以将代码视为只是foo(x)。如果 的地址x没有被其他任何地方占用,那么它就不再需要有地址,并且可以进入寄存器。

现在您可以想象将这种分析扩展到更复杂的代码。

    int x = foo1(), y = foo2();
    int *p;
    p = cond ? &x : &y;
    return *p;

在godbolt上试试

从概念上讲,这可以连续重写为

return *(cond ? &x : &y);
return cond ? *&x : *&y;
return cond ? x : y;

现在x,y不再需要有地址,p也不再需要存在。

所以换句话说,编译器不会试图“模拟”&操作符;相反,它会尝试重构代码以使其不再需要。

这不可能的最常见情况是变量的地址被传递给另一个函数。

int x;
foo(&x);

除非foo可以内联或其他类型的过程间分析可用,否则编译器确实必须将某些东西的地址传递给foo,因此x必须存在于内存中,至少在那一刻是这样。当然,编译器可以选择在之后立即将其移动到寄存器中,如果不再需要它的地址,则将其保留在函数的其余部分中;变量是存在于内存中还是存在于寄存器中的问题不需要一直固定。

于 2020-07-06T13:27:12.853 回答
1

首先,请注意register优化期间非变量发生的事情超出了 C 语言的范围。还有另一种选择:从机器代码中完全删除变量。

我的问题是,如果编译器自己决定将变量存储在寄存器中而不是溢出它,那么在代码执行期间 & 运算符会发生什么?

如果编译器发现操作符的存在,它就不太可能将变量放入寄存器中&。事实上,我曾经使用过的每个现实世界的编译器都会将这样的变量放在堆栈或静态存储内存中,从而使其可寻址。

于 2020-07-06T13:09:04.877 回答
1

在解析 C 代码时尽快生成程序集的流编译器(例如 tinycc)根本不会将变量放入寄存器,除非这些变量具有register存储类说明符。另一方面,构建抽象语法树的非流式编译器只有在看到整个块后才能决定是否将某些内容放入寄存器中。然后它可以确定永远不需要变量的地址(或者通过其地址对变量的所有访问都可以优化为直接寄存器访问)。

由于 C 不能在运行时在其静态提供的代码的上下文中解释动态输入的一段 C 代码(没有_Eval(read_string())用户可以输入的地方printf("%p\n",(void*)&some_local);),因此在运行时不会有任何意外的取址。在 C 编译器完成一个块之后,它知道其中的每个局部变量将如何被使用。

于 2020-07-06T13:26:50.547 回答