常量正确性是否给了编译器更多的优化空间?

我知道它提高了可读性,使程序更不容易出错,但是它在多大程度上提高了性能呢?

另外,引用和 const指针的主要区别是什么?我会假设它们在记忆中的存储方式不同,但怎么会这样呢?

8396 次浏览

它可以稍微提高性能,但是只有在通过对象的声明直接访问该对象的情况下才能提高性能。不能优化引用参数之类的,因为可能有其他路径到一个没有最初声明 const 的对象,并且编译器通常无法判断您正在引用的对象是否实际上声明了 const,除非这是您正在使用的声明。

如果您使用常量声明,编译器将知道外部编译的函数体等不能修改它,因此您将从中获益。当然,像 const int 这样的东西是在编译时传播的,所以这是一个巨大的胜利(与仅仅使用 int 相比)。

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

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

常量正确性通常无助于提高性能,大多数编译器甚至懒得在前端追踪 constness。根据具体情况,将变量标记为 const 会有所帮助。

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

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

声明指向常量的指针或常量的引用从来不会帮助任何编译器优化任何东西。(不过请参阅本答案底部的更新。)

The const declaration only indicates how an identifier will be used within the 范围 of its declaration; it does not say that the underlying object can not change.

例如:

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

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

If the compiler knows enough about the caller of foo() and the contents of bar() that it can prove bar() does not modify *p, then 它也可以在不使用 const 声明的情况下执行该证明.

但总的来说,这是事实。因为 const只在声明的范围内有效,编译器已经可以看到在该范围内如何处理指针或引用; 它已经知道您没有修改基础对象。

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

那么调用 foo()的函数呢? 比如:

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

编译器能证明它打印37吗,因为 foo()采用 const int *

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

const唯一允许优化的情况是:

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

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

为了回答你的“旁注”问题,(a)常量指针是一个指针; (b)常量指针可以等于 NULL。你是正确的,内部表示(即地址)很可能是相同的。

[更新]

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

C99标准第6.7.3.1(4)条规定:

During each execution of B, let L be any lvalue that has &L based on P. If L is used to 访问它指定的对象 X 的值,并且 X 也被修改(以任何方式) , 然后适用以下要求: T 不得是合格的。

(这里的 B 是一个基本块,其上的 P 是一个限制指针 T,在作用域中。)

So if a C function foo() is declared like this:

foo(const int * restrict p)

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

因此,原则上,将 restrict与指向常量的指针相结合可以实现上述两种优化。我想知道,是否有编译器真正实现了这种优化?(海湾合作委员会4.5.2至少没有。)

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

一方面,如果声明一个全局变量 const,可以将其放在库或可执行文件的只读部分,从而使用只读 mmap 在多个进程之间共享它。至少如果您在全局变量中声明了大量数据,那么在 Linux 上这将是一个巨大的内存优势。

另一种情况是,如果声明一个常量全局整数,或者 float 或 enum,编译器可能只需将常量内联,而不需要使用变量引用。虽然我不是编译器专家,但我相信这样会快一点。

引用只是底层的指针,在实现方面。

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

对 const 的指针和引用通常无助于优化,因为 const 限定在某些情况下可以合法地放弃,而且对象可以通过不同的非 const 引用进行修改。另一方面,将一个对象声明为 const 会很有帮助,因为它保证对象不能被修改(即使传递给编译器不知道其定义的函数时也是如此)。这使得编译器可以将 const 对象存储在只读内存中,或者将其值缓存在一个集中的位置,从而减少了对副本的需求,并检查是否进行了修改。

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

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

  • const_cast
  • mutable

const_cast意味着即使通过常量引用或常量指针传递对象,函数也可能抛弃常量并修改对象(如果对象一开始就不是常量,则允许这样做)。

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

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

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

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

在这里,编译器知道不必检查 bar()的主体(在当前的翻译单元中可能不可见)就可以打印 42,因为对 foo的所有修改都是非法的(this is the same as Nemo's example)。

However, this is also possible without marking foo as const by declaring bar() as

extern void bar(const int *restrict p);

在许多情况下,程序员实际上需要 restrict限定的指向 const的指针,而不是纯粹的指向 const的指针作为函数参数,因为只有前者能够保证指向对象的可变性。

至于你问题的第二部分: 对于所有实际目的,C + + 引用可以被看作是一个常量指针(而不是一个指向常量值的指针!)与自动间接-它不是任何’安全’或’快’比一个指针,只是更方便。