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

时间:2013-05-14 12:12:17

标签: c pointers const constants assignment-operator

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

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

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

2)即使由于一些奇怪的原因我们被允许更改常量值,为什么要使用指针(允许使用指针)更改只读const限定变量的值之间的区别警告)并通过使用赋值操作(这是不允许的,并给我们一个错误)?

//prog1
#include <stdio.h>

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

警告:初始化会丢弃指针目标类型|

中的限定符
//prog2
#include <stdio.h>

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

错误:指定只读变量'i'|

编辑H2CO3

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

//prog3
#include <stdio.h>

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

2 个答案:

答案 0 :(得分:8)

  

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限定类型的左值修改具有const限定类型的对象,如

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

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

为初始化发出了诊断消息

int *ptr = &i;

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

  

以下其中一项应成立:

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

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

int *ptr = (int*)&i;

然而,人们无法抛弃const中的i

通过指向非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明确表示&#34;我不希望修改该对象&#34;。

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

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

答案 1 :(得分:2)

  

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

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

  

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

再次:不允许,因此警告。 (警告要认真对待 - 你似乎没有给他们任何意义......)只是编译器不知道指针指向某些const - 限定对象(因为它被声明为非const T *)。

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

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

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