78

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

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

4

7 回答 7

70

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

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

const声明仅表明标识符将如何在其声明范围内使用;它并不是说底层对象不能改变。

例子:

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

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

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

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

所以简而言之,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。(实际上,您从不引用的局部变量不会受益,因为编译器已经可以看到您是否在其范围内对其进行了修改。)

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

[更新]

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

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

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

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

因此,如果像这样声明 C 函数foo()

foo(const int * restrict p)

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

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

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

于 2011-06-11T03:11:30.650 回答
7

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

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

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

但是,这也是可能的,无需标记fooasconst通过声明bar()

extern void bar(const int *restrict p);

在许多情况下,程序员实际上想要restrict-qualified pointers-to-const而不是普通的 pointers-to-const作为函数参数,因为只有前者才能保证指向对象的可变性。

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

于 2011-06-11T09:08:13.117 回答
6

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

  • const_cast
  • mutable

const_cast意味着即使您通过 const 引用或 const 指针传递对象,该函数也可能会丢弃 const 并修改对象(如果对象不是 const 开头则允许)。

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

const在这里帮助开发人员及早发现逻辑错误。

于 2011-06-11T09:00:56.690 回答
4

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

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

于 2011-06-11T02:55:38.040 回答
2

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

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

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

于 2011-06-11T03:33:33.390 回答
1

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

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

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

于 2011-06-11T03:27:50.817 回答
0

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

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

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

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

于 2011-06-11T02:55:36.517 回答