繁体   English   中英

const-correctness 是否给编译器更多的优化空间?

[英]Does const-correctness give the compiler more room for optimization?

我知道它提高了可读性并使程序更不容易出错,但是它对性能的提高有多大呢?

顺便说一句,引用和const指针之间的主要区别是什么? 我会假设它们以不同的方式存储在 memory 中,但怎么会呢?

[编辑:好的,所以这个问题比我一开始想的要微妙。]

声明指向 const 或引用 const 的指针永远不会帮助任何编译器优化任何东西。 (尽管请参阅此答案底部的更新。)

const声明仅指示标识符将如何在其声明的scope中使用; 并不是说底层 object 不能改变。

例子:

int foo(const int *p) {
    int x = *p;
    bar(x);
    x = *p;
    return x;
}

编译器不能假设*p没有被bar()调用修改,因为p可能是(例如)指向全局 int 的指针,而bar()可能会修改它。

如果编译器足够了解foo()的调用者和bar()的内容,它可以证明bar()没有修改*p ,那么它也可以在没有 const 声明的情况下执行该证明

但总的来说,这是正确的。 因为const仅在声明的 scope 内有效,所以编译器已经可以看到您如何处理 scope 内的指针或引用; 它已经知道您没有修改底层 object。

所以简而言之,所有const在这种情况下所做的都是防止你犯错误。 它不会告诉编译器任何它不知道的东西,因此它与优化无关。

调用foo()的函数呢? 喜欢:

int x = 37;
foo(&x);
printf("%d\n", x);

编译器能否证明这打印了 37,因为foo()需要一个const int *

不。即使foo()采用指向 const 的指针,它也可能会丢弃 const 并修改 int。 (这不是未定义的行为。)在这里,编译器通常不能做出任何假设。 如果它对foo()有足够的了解来进行这样的优化,它就会知道即使没有const

const唯一可能允许优化的情况是这样的:

const int x = 37;
foo(&x);
printf("%d\n", x);

在这里,通过任何机制修改x (例如,通过获取指向它的指针并丢弃const )是调用未定义行为。 所以编译器可以自由地假设你不这样做,它可以将常量 37 传播到 printf() 中。 这种优化对于您声明const的任何 object 都是合法的。 (实际上,您从不引用的局部变量不会受益,因为编译器已经可以看到您是否在其 scope 中对其进行了修改。)

要回答您的“旁注”问题,(a) const 指针是指针; (b) const 指针可以等于 NULL。 您是正确的,内部表示(即地址)很可能是相同的。

[更新]

正如克里斯托夫在评论中指出的那样,我的回答是不完整的,因为它没有提到restrict

C99 标准的第 6.7.3.1 (4) 节说:

在每次执行 B 期间,令 L 为基于 P 的具有 &L 的任何左值。如果 L 用于访问它指定的 object X 的值,并且 X 也被修改(通过任何方式),则适用以下要求: T 不应是 const 限定的。 ...

(这里 B 是一个基本块,P 是指向 T 的限制指针,位于 scope 中。)

因此,如果 C function foo()声明如下:

foo(const int * restrict p)

...那么编译器可能假设在 p 的生命周期内没有对*p p修改——即在foo()的执行期间——因为否则行为将是未定义的。

因此,原则上,将restrict与指向 const 的指针相结合可以启用上面忽略的两种优化。 我想知道是否有任何编译器实际上实现了这样的优化? (至少 GCC 4.5.2 没有。)

请注意, restrict仅存在于 C 中,而不存在于 C++(甚至 C++0x 中),除非作为特定于编译器的扩展。

在我的脑海中,我可以想到两种情况,适当的const限定允许额外的优化(在整个程序分析不可用的情况下):

const int foo = 42;
bar(&foo);
printf("%i", foo);

在这里,编译器知道打印42而不必检查bar()的主体(在当前的翻译单元中可能不可见),因为对foo的所有修改都是非法的(这与Nemo 的示例相同)。

但是,这也可以通过将bar()声明为而不将foo标记为const

extern void bar(const int *restrict p);

在许多情况下,程序员实际上想要restrict限定的指向const的指针而不是普通的指向const的指针作为 function 参数,因为只有前者对指向对象的可变性做出任何保证。

至于问题的第二部分:出于所有实际目的,C++ 引用可以被认为是具有自动间接的常量指针(不是指向常量值的指针) - 它不是任何“更安全”或“更快”比指针。 只是更方便。

C++ 中的const有两个问题(就优化而言):

  • const_cast
  • mutable

const_cast mean that even though you pass an object by const reference or const pointer, the function might cast the const-ness away and modify the object (allowed if the object is not const to begin with).

mutable意味着即使 object 是const ,它的某些部分可能会被修改(缓存行为)。 此外,可以在const方法中修改指向(而不是拥有)的对象,即使它们在逻辑上是 object state 的一部分。 最后全局变量也可以修改...

const可以帮助开发人员及早发现逻辑错误。

const 正确性通常无助于提高性能; 大多数编译器甚至不费心去跟踪前端以外的常量。 根据具体情况,将变量标记为 const 会有所帮助。

引用和指针在 memory 中的存储方式完全相同。

这实际上取决于编译器/平台(它可能有助于优化某些尚未编写的编译器,或者您从未使用过的某些平台)。 C 和 C++ 标准除了给出某些功能的复杂性要求外,只字未提性能。

对 const 的指针和引用通常无助于优化,因为在某些情况下可以合法地抛弃 const 限定条件,并且 object 可以通过不同的非常量引用进行修改。 另一方面,将 object 声明为 const 可能会有所帮助,因为它保证了 object 不能被修改(即使传递给编译器不知道其定义的函数)。 这允许编译器将 const object 存储在只读 memory 中,或将其值缓存在集中位置,从而减少复制和检查修改的需要。

指针和引用通常以完全相同的方式实现,但这完全取决于平台。 如果您真的感兴趣,那么您应该查看为您的平台和程序中编译器生成的机器代码(如果您确实使用的是生成机器代码的编译器)。

一件事是,如果您声明一个全局变量 const,则可以将其放在库或可执行文件的只读部分中,从而使用只读 mmap 在多个进程之间共享它。 至少如果您在全局变量中声明了大量数据,这可能是 Linux 上的一个大 memory 胜利。

另一种情况,如果您声明一个常量全局 integer 或浮点数或枚举,编译器可能只将常量内联而不是使用变量引用。 尽管我不是编译器专家,但我相信这会更快一些。

引用只是下面的指针,实现方式。

它可以对性能有所帮助,但前提是您直接通过其声明访问 object。 引用参数等无法优化,因为可能存在指向最初未声明为 const 的 object 的其他路径,并且编译器通常无法判断您引用的 object 是否实际声明为 const,除非这是您正在使用的声明。

如果您使用 const 声明,编译器将知道外部编译的 function 主体等无法修改它,因此您可以从中受益。 当然,像 const int 这样的东西是在编译时传播的,所以这是一个巨大的胜利(与一个 int 相比)。

引用和指针的存储方式完全相同,只是在语法上表现不同。 引用基本上是重命名,因此相对安全,而指针可以指向许多不同的东西,因此更强大且更容易出错。

我猜 const 指针在架构上与引用相同,因此机器代码和效率是相同的。 真正的区别在于语法——引用是一种更简洁、更易于阅读的语法,并且由于您不需要指针提供的额外机制,因此在风格上更倾向于引用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM