const-correctness是否为编译器提供了更多优化空间?

时间:2011-06-11 02:41:51

标签: c++ c pointers const const-correctness

我知道它提高了可读性并使程序不易出错,但它能提高性能多少呢?

另一方面,参考和const指针之间的主要区别是什么?我会假设它们以不同的方式存储在内存中,但是如何呢?

7 个答案:

答案 0 :(得分:69)

[编辑:好的,所以这个问题比我最初想的更微妙。]

声明指向const的指针或const-reference-const永远不会帮助任何编译器优化任何东西。 (虽然请参阅本答复底部的更新。)

const声明仅指示如何在其声明的范围中使用标识符;它没有说基础对象不能改变。

示例:

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

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

如果编译器对foo()的调用者以及可以证明bar()不能修改bar()的{​​{1}}的内容有足够的了解,那么它可以也可以在没有const声明的情况下执行该证明

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

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

调用const的函数怎么样?像:

foo()

编译器可以证明这打印37,因为int x = 37; foo(&x); printf("%d\n", x); 需要foo()吗?

没有。即使const int *采用指向const的指针,它也可能会抛出const-ness并修改int。 (这是未定义的行为。)同样,编译器一般不能做任何假设;如果它足够了解foo()进行此类优化,即使没有foo(),也会知道。

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

const

在这里,通过任何机制修改const int x = 37; foo(&x); printf("%d\n", x); (例如,通过获取指针并抛弃x)是调用未定义的行为。因此,编译器可以自由地假设您不这样做,并且它可以将常量37传播到printf()中。对于您声明const的任何对象,这种优化都是合法的。 (实际上,您从未参考过的局部变量不会受益,因为编译器已经可以看到您是否在其范围内修改它。)

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

[更新]

正如Christoph在评论中指出的那样,我的回答是不完整的,因为它没有提及const

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

  

在每次执行B期间,让L为任何基于P的& L的左值。如果L用于   访问它指定的对象X的值,并且X也被修改(通过任何方式),    那么以下要求适用:T不应该是合格的。 ...

(这里B是一个基本块,P是一个限制指针到T的范围。)

因此,如果C函数restrict被声明如下:

foo()

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

因此原则上,将foo()与指向const的指针相结合可以启用上面所述的两个优化。我想知道,任何编译器都会实现这样的优化吗? (GCC 4.5.2,至少,没有。)

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

答案 1 :(得分:6)

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

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

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

但是,通过将foo声明为

,也可以将const标记为bar()
extern void bar(const int *restrict p);

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

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

答案 2 :(得分:5)

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

  • const_cast
  • mutable

const_cast意味着即使你通过const引用或const指针传递一个对象,该函数也可能会抛出const-ness并修改对象(如果对象不是const,则允许该对象)。 / p>

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

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

答案 3 :(得分:4)

const-correctness一般没有帮助表现;大多数编译器甚至都不愿意追踪超出前端的常量。将变量标记为常量可能有所帮助,具体取决于具体情况。

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

答案 4 :(得分:2)

这实际上取决于编译器/平台(它可能有助于对尚未编写的某些编译器进行优化,或者在某些您从未使用过的平台上进行优化)。除了为某些函数提供复杂性要求之外,C和C ++标准对性能没有任何说明。

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

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

答案 5 :(得分:1)

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

另一种情况是,如果声明一个常量全局整数,或浮点数或枚举,编译器可能只能将常量内联而不是使用变量引用。我相信虽然我不是编译专家,但速度要快一点。

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

答案 6 :(得分:0)

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

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

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

我猜const指针在体系结构上与引用相同,因此机器代码和效率将是相同的。真正的区别在于语法 - 引用是一种更清晰,更易于阅读的语法,并且由于您不需要指针提供的额外机制,因此引用将是风格上的首选。