我知道它提高了可读性并使程序不易出错,但它能提高性能多少呢?
另一方面,参考和const
指针之间的主要区别是什么?我会假设它们以不同的方式存储在内存中,但是如何呢?
答案 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指针在体系结构上与引用相同,因此机器代码和效率将是相同的。真正的区别在于语法 - 引用是一种更清晰,更易于阅读的语法,并且由于您不需要指针提供的额外机制,因此引用将是风格上的首选。