为什么我们允许更改“const”限定变量的值?为什么指针允许这样做,但不允许赋值?

考虑以下2个程序prog1prog2。如果我尝试使用指针ptr更改const限定变量i的值,我得到警告(而不是错误) "initialization discards qualifiers from pointer target type|" ,但程序运行然后显示新值。但是如果我尝试使用赋值语句在第二个程序中更改i的值,我得到assignment of read-only variable 'i'|错误 (不是警告) assignment of read-only variable 'i'|

以下是此前提产生的混淆:

1)为什么我们允许在任何情况下更改只读const限定变量的值?它是否会破坏使用const限定符的目的?如果我们尝试这样做,我们不应该得到错误吗?

2)即使由于一些奇怪的原因我们被允许更改常量值,为什么在使用指针(允许,带警告)和使用赋值更改只读const限定变量的值之间进行区分操作(根本不允许,并给我们一个错误)?

 //prog1 #include  int main () { const int i=8; int *ptr=&i; *ptr=9; printf("%d",*ptr); //Prints new value nevertheless } 

警告:初始化从指针目标类型中丢弃限定符

 //prog2 #include  int main() { const int i=8; i=10; printf("%d",i); } 

错误:分配只读变量’i’|

编辑H2CO3

在这里,我多次更改const限定变量的值。我只得到一个警告,与prog1的相同

 //prog3 #include  int main () { const int i=8; int *ptr=&i; *ptr=9; *ptr=10; printf("%d",*ptr); //Prints 10 } 

1)为什么我们允许在任何情况下更改只读const限定变量的值? 它不会破坏使用const限定符的目的吗?

尝试通过赋值运算符更改const限定对象是违反约束:

6.5.16在约束条件下:

2赋值运算符应具有可修改的左值作为其左操作数。

可修改的左值在6.3.2.1(1)中定义:

可修改的左值是一个左值,它没有数组类型,没有不完整的类型, 没有const限定类型 ,如果是结构或联合,则没有任何成员(包括,递归地,任何成员)或具有const限定类型的所有包含的聚合或联合的元素。

作为约束违规,它需要来自编译器的诊断消息,符合5.1.1.3(1):

如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合的实现应生成至少一个诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现 – 定义。 在其他情况下不需要产生诊断消息。

但是,不需要实现来拒绝无效程序,因此诊断消息也可以是警告而不是错误。

但是,修改通过不具有const限定类型的左值声明为const的对象不是约束违例,尽管它调用未定义的行为,6.7.3(6):

如果尝试通过使用具有非const限定类型的左值来修改使用const限定类型定义的对象,则行为未定义。

由于它不是约束违规也不是无效语法,因此甚至不需要发出诊断消息。

如果我们试图这样做,我们不应该得到错误吗?

如果尝试通过具有const限定类型的左值修改对象,则必须获取诊断消息。

由于这种严重违反声明的意图,大多数编译器在这些情况下会发出错误。

如果您尝试通过具有非const-qualifed类型的左值修改具有const限定类型的对象,如下所示

 const int i=8; int *ptr=&i; *ptr=9; 

通过表达式*ptr = 9修改i的尝试调用未定义的行为,但不是约束违规(或语法错误),因此不需要诊断消息(并且没有给出)。

为初始化发出了诊断消息

 int *ptr = &i; 

因为这又是一个约束违规,根据6.5.16.1(1):

以下其中一项应持有:

  • 左操作数具有primefaces,限定或非限定算术类型,右边有算术类型;
  • 左操作数具有与右侧类型兼容的结构或联合类型的primefaces,限定或非限定版本;
  • 左操作数具有primefaces,限定或非限定指针类型,并且(考虑左值操作数在左值转换后将具有的类型)两个操作数都是指向兼容类型的限定或非限定版本的指针, 左侧指向的类型具有全部右边指出的类型的限定词 ;
  • 左操作数具有primefaces,限定或非限定指针类型,并且(考虑左值操作数在左值转换后将具有的类型)一个操作数是指向对象类型的指针,另一个是指向合格或非限定版本的指针void,左边指向的类型具有右边指向的所有类型的限定符;
  • 左操作数是一个primefaces,限定或非限定指针,右边是一个空指针常量; 要么
  • 左操作数的类型为atomic,qualified或nonqualified _Bool,右边是指针。

然而,该诊断通常是警告而不是错误,因为可能会明确地将const

 int *ptr = (int*)&i; 

而一个人不能抛弃iconst

如果指向的对象是可修改的,则通过指向非const限定对象类型的指针来修改对象,该指针是通过从指向const限定对象类型的指针中删除const而获得的。 愚蠢的例子:

 int i = 8; const int *cptr = &i; // valid, no problem adding const int *mptr = (int*)cptr; *mptr = 9; // no problem, pointee is non-const 

2)即使由于一些奇怪的原因我们被允许更改常量值,为什么在使用指针(允许,带警告)和使用赋值更改只读const限定变量的值之间进行区分操作(根本不允许,并给我们一个错误)?

直接分配给具有const限定类型的对象不仅违反了约束,而且明显违反了所声明的语义。 声明一个对象const明确地说“我不希望修改该对象”。

通过指向非const限定类型的指针修改对象不是约束违规,如果指针对象具有const限定类型,则只有未定义的行为。 允许将指向const限定类型的指针转​​换为指向相应的非const限定类型的指针,并且通过该指针修改指针可能是有效的,因此您只获得警告,并且仅在未进行转换时明确。

在给定的简短示例中,编译器可以检测到指针对象具有const限定类型,因此修改调用未定义的行为,但通常这样很难,并且通常无法检测。 因此,编译器甚至不会尝试检测简单的情况,这是不值得的。

为什么我们允许在任何情况下更改只读const限定变量的值?

我们不是。 我不明白你为什么这么认为,也没有哪个例子表明这一点。

为什么使用指针改变只读const限定变量的值之间的区别(允许,带警告)

再说一遍:这是不允许的,因此警告。 (警告应该被认真对待 – 你似乎没有给它们任何意义……)只是编译器不知道指针指向一些const限定对象(因为它被声明为非const) T * )。

至于为什么更改变量有效:

解释#1:它是未定义的行为(违反约束),所以它可以做任何事情。

解释#2:可能它只是像本地自动变量一样存储在堆栈中,你确实可以改变它。