问题描述
让我们采用以下两个函数:
#include<stdio.h>
void my_print1(char* str) {
// str = "OK!";
printf("%s\n",str);
}
void my_print2(char* const str) {
// str = "OK!";
printf("%s\n",str);
}
它们都产生same assembly
:
那么 const
是如何在这里强制执行的?例如,如果我取消注释 str = "OK!;
,它当然会在第一个函数调用中起作用,但在第二个 (error: assignment of read-only parameter ‘str’
) 中不起作用。
局部变量的 const
-ness 是否只是编译器构造,它负责检查这一点,或者如果两个函数的程序集相同,它如何工作?注意:这只是 C,不是 C++(因为我认为他们对待 const 是不同的)。
解决方法
正确,在大多数实现中,它只是一个编译器构造。
在典型的主流操作系统实现中,有一种方法可以将具有 const
存储持续时间的 static
对象放置在实际上由 CPU 的内存管理单元 (MMU) 写保护的内存中,例如.text
或 .rodata
部分。然后尝试写入它,如果在编译时没有阻止,将在运行时导致陷阱。但是硬件写保护适用于大内存块(例如整个页面)。对于 auto
对象,例如位于堆栈内存或寄存器中的局部变量或函数参数,没有好的方法可以做到这一点。在堆栈上,由于它们与非 const
变量混合在一起,因此硬件写保护的粒度不足以应用于它们,并且在任何情况下不断更改都会非常昂贵(它需要调用到操作系统)。大多数机器上的寄存器根本不能设为只读。
由于没有好的方法在运行时保护它们,编译器通常为 const
自动对象生成与非 const
完全相同的代码。
您可能会在某些情况下看到差异,因为 const
通知编译器该对象的值不应该改变,因此编译器可以假设它没有改变。例如,如果您将指向非const
对象的指针传递给另一个函数,则编译器必须假设该对象的值可能已更改,并会在每次函数调用后从内存中重新加载它。但是 const
对象可能会在整个函数调用期间将其值缓存在寄存器中,或者如果可能的话只是优化为立即数。
函数参数和其他局部变量上的 const
限定符通常对生成的代码没有影响。它只是告诉编译器阻止对变量赋值。
理论上,它可以生成防止通过其他方式修改变量的代码。例如。如果你有
void my_print2(char* const str) {
*(char *)&str = "OK!";
printf("%s\n",str);
}
赋值会导致未定义的行为,但不会导致错误(尽管编译器可能会警告丢弃常量)。但是编译器理论上可以将 str
存储在标记为只读的内存中;在这种情况下,分配将导致分段错误。这通常不会对函数参数进行,因为很难将其与使用堆栈用于自动数据相协调。 (Nate Eldredge 的回答更好地解释了这一点。)
编译器通过拒绝生成不合规的代码来强制执行 const
。如果 my_print2
源尝试修改 str
,编译器会发出错误消息。
关于您的代码:
void my_print2(char* const str)...
这有点毫无意义,因为它只能限制函数对指针本身的值可以做什么,而不能对它指向的内存做什么,所以:
void my_print2(char* const str)
{
str++; // Not allowed,compile time error.
*str = 'A'; // Okay.
}
不需要修改所指向内容的函数,应声明为 const <type> *
以让调用者确信您的函数(在本例中为打印)不会更改其数据:
void my_print3(const char* str)
{
str++; // Okay
*str = 'A'; // Not allowed,compile time error.
}
void my_print4(char const* str)
{
str++; // Okay
*str = 'A'; // Not allowed,compile time error.
}