const 修饰符复习之const char*, char const*, char*const的区别

const char*, char const*, char*const的区别问题,Bjarne在他的The C++ Programming Language里面给出过一个助记的方法: 
把一个声明从右向左读。 
php

char * const cp; ( * 读成 pointer to ) linux

cp is a const pointer to char程序员

cp 是一个指向char的常量指针编程


const char * p; 
ubuntu

p is a pointer to const char;数组

p是一个指向常量char的指针ide

char const * p;函数

同上由于C++里面没有const*的运算符,因此const只能修饰属于前面的类型char,所以char const * p等价于const char *p。 
C++标准规定,const关键字放在类型或变量名以前等价的。
工具

深刻理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p测试

1、可能的组合:

     (1)const char*p

     (2)char const*p

     (3)char *const p
     (4)const char **p

     (5)char const**p

     (6)char *const *p

     (7)char **const p

     固然还有在(5)、(6)、(7)中再插入一个const的若干状况,不过度析了以上7中,其余的就可类推了!

2、理解助记法宝:

    1。关键看const 修饰谁。

     2。因为没有 const *的运算,若出现 const * 的形式,则const其实是修饰前面的。

     好比:char const*p,因为没有const*运算,则const其实是修饰前面的char,所以char const*p等价于const char*p。也就是说上面7种状况中,(1)和(2)等价。 同理,(4)和(5)等价。在(6)中,因为没有const*运算,const实际上修饰的是前面的char*,但不能在定义时转换写成 const(char *)*p,由于在定义是"()"是表示函数。

3、深刻理解7种组合

     (0)程序 在执行时为其开辟的空间皆在内存(RAM)中,而RAM里的内存单元是可读可写 的;指针只是用来指定或定位要操做的数据的工具,只是用来读写RAM里内存单元的工做指针 。若对指针不加任何限定,程序中一个指针能够指向RAM中的任意位置(除了系统敏感区,如操做系统内核所在区域)并对其指向的内存单元进行读和写操做(由RAM的可读可写属性决定);RAM里内存单元的可读可写属性不会由于对工做指针的限定而变化(见下面的第4点),而全部对指针的各类const限定说白了只是对该指针 的 读写权限 (包括读写位置)进行了限定 。


     (1)char *p:p是一个工做指针,能够用来对任意位置 (非系统敏感区域)进 行读操做和写操做 ,一次读写一个字节(char占一个字节)。
     (2)const char*p或者char const *p(由于没有const*p运算,所以const修饰的仍是前面的char):能够对任意位置(非系统敏感区域)进行“只读” 操做。(“只读”是相对于char *p来讲所限定的内容)
     (3)char *const p(const 修饰的是p):只能对“某个固定的位置” 进 行读写操做,而且在定义p时就必须初始化(由于在后面不能执行“p=..”的操做,所以就不能在后面初始化,所以只能在定义时初始化)。(“某个固定的位 置”是相对于char *p来讲所限定的内容)
能够总结以上3点为:char *p中的指针p一般是”万能”的工做指针 ,而(2)和(3)只是在(1)的基础上加了些特定的限制 ,这些限制在程序中并非必须的,只是为了防止程序员的粗枝大叶而产生事与愿违的错 误。
另外,要明白“每块内存空间均可有名字;每块内存空间内容皆可变(除非有所限) ” 。好比函数里定义的char s[]="hello";事实上在进程的栈内存里开辟了6个变量共6个字节的空间,其中6个字符变量的名字分别为:s1[0]、s1[1]、 s1[2]、s1[3]、s1[4]、s1[5](内容是'\0')

{

   待验证 : 还有一个4字节的指针变量s 。不过s是“有所限制”的,属于char *const类型,也就是前面说的   (3)这种状况,s一直指向s[0],    即(*s==s[0]=='h'),能够经过*s='k'来改变s所指向的 s[0]的值,但不能执行(char *h=“aaa”;s=h;)来对s另外赋值。

}

     (4)上面的(2)和(3)只是对p进行限定,没有也不能对p所指向的空间进行限定,对于"char s[]="hello";const char*p=s;" 虽然不能经过*(p+i)='x'或者p[i]='x'来修改数组元素s[0]~s[4]的值,但能够经过*(s+i)='x'或者s[i]='x'来修改原数组元素的值--RAM里内存单元的可读可写属性不因对工做指针的限定而改变,而只会因对其自己的限定而改变。如const char c=‘A’,c是RAM里一个内存单元(8字节)的名字,该内存单元的内容只可读,不可写。

     (5)const char **p或者char const**p :涉及两个指针p和 *p。因为const修饰char ,对指针p没有任何限定,对指针*p进行了上面状况(2)的限定。

    (6)char *const *p:涉及两个指针p和 *p。因为const修饰前面的char*,也就是对p所指向的内容*p进行了限定(也属于前面的状况(2))。而对*p来讲,因为不能经过"*p=..."来进行另外赋值,所以属于前面的状况(3)的限定。

    (7)char **const p : 涉及两个指针p和 *p,const修饰p,对p进行上面状况(3)的限定,而对*p,没有任何限定。

4、关于char **p 、const char **p的类型相容性问题

    1。问题

        char *p1;const *p2=p1;//合法

        char **p1;const  char**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type

        char **p1;char const**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type

        char**p1;char*const*p2=p1;//合法

    2。判断规则

        明确const修饰的对象!对于指针p1,和p2,若要使得p2=p1成立,则可读作 :

             “p1是指向X类型的指针,p2是指向“带有const限定”的X类型的指针 “。

        char *p1;const *p2=p1;//合法:p1是指向(char)类型的指针,p2是指向“带有const限定"的(char)类型的指针。

        char **p1;const  char**p2=p1;//不合法:p1是指向(char*)类型的指针,p2是指向 ((const char)*)类型的指针。

        char **p1;char const**p2=p1;//不合法;与上等价。

        char**p1;char*const*p2=p1;//合法:  p1是指向(char *)类型的指针,p2是指向“带有const限定"的(char*)类型的指针。

5、其余

    1。 含有const的单层或双层指针的统一读法:

         “p是一个指针,是一个[“带有const限定”的]指向[”带有const限定”的]X类型的指针”。

    2。定义时const修饰的对象是肯定的,但不能在定义时加括号,否则就和定义时用“()”表示的函数类型相混淆了!所以定义时不能写(char *)const *p或者(const char) **p。

6、问题探讨(因为博文后的留言有字符数目限制,将回复移到这里)

  问题1 (见博文后留言):讲解很是好,不过有个问题想探讨下: 例如: const char wang[]={"wang"}; char *p; p=wang; 是错误的。 因此char *p中的P不能指向常变量。 (1)须要补充纠正。

  回复 : 你好!谢谢指正!我在ubuntu 10.04(gcc 4.4.3)下作了以下测试: 
   //test_const.c
   #include<stdio.h>
   int main()
   {
     const char wang[]={"wang"};
     char *p;
     p=wang;
     p[2]='c';
     printf("p is %s\n",p);
     return 0;
  }
编译 :

       gcc -o test_const test_const.c
输出以下 :
       test_const.c: In function ‘main’:
       test_const.c:17: warning: assignment discards qualifiers from pointer target type
执行:

       ./test_const
       p is wacg
结论: 根据本博文中第四点--相容性问题,将const型的wang赋值给p是不合法的。但编译器对其的处理只是警告,所以执行时经过p修改了只读区域的数据。这应该是该编译器处理不严所致后果,不知你用的什么编译器?

    问题2  回答 http://www.linuxsir.org/bbs/showthread.php?t=239058 提到的问题

在c语言里

// test.c
int main() {
   const char* s1 = "test";
   char *s2 = s1;
   s2 = "It's modified!";
   printf("%s\n",s1);
}
out: It's modified!;
这样也能够吗? 照个人理解岂不是const限定符在c语言里只是摆设一个?

     回复:

(1)首先,以上代码编译时会出错warning: initialization discards qualifiers from pointer target type,

     由于char *s2 = s1和问题1提到的同样,不符合相容规则。

 


(2)输出结果是正确的,代码分析以下:

  int main() {
   const char* s1 = "test";  //  在只读数据区(objdump -h test后的.rodata区)开辟5字节存储"test",并用s1指向首字符‘t’。 
   char *s2 = s1;                //  s2也指向只读数据区中“test”的首字符't'。 
   s2 = "It's modified!";      // 在只读数据区开辟15字节存储"It's modified!",并将s2由指向't'转而指向"It's modified!"的首字符'I'。 
   printf("%s\n",s1);          // 从s1所指的‘t’开始输出字符串"test"。 
}


(3)总结:提问者的误区在于,误觉得s2 = "It's modified!"是对“test”所在区域的从新赋值,其实这里只是将“万能”工做指针s2指向另一个新开辟的区域而已。好比若在char *s2 = s1后再执行s2[2]='a'则是对“test”的区域进行了写操做,执行时会出现段错误。但这个段错误其实与const没有关系,由于“test”这块区域自己就是只读的。为了防止理解出错,凡事对于对指针的赋值(好比 s2 = "It's modified!" ),则将其读作:将s2指向“ It's modified! ”所在区域的首字符。

 


(4)额外收获:执行gcc -o test test.c后,“test”、“It's modified!”、"%s\n"都被做为字符串常量存储在二进制文件test的只读区

    域 (.rodata)。事实上,一个程序从编译到运行,对变量空间分配的状况以下:

A。赋值了的全局变量或static变量=>放在可执行文件的.data段。

B。未赋值的全局变量或static变量=>放在可执行文件的.bss段。

C。代码中出现的字符串常量或加了const的A=>放在可执行文件的.rodata段。

D。通常的局部变量=>在可执行文件中不占空间,在该二进制文件做为进程在内存中运行时才为它分配栈空间。

E。代码中malloc或new出的变量=>在可执行文件中不占空间,在该二进制文件做为进程在内存中运行时才为它分配堆空间。

    问题3:(待进一步分析) 验证博文中 三(3)提到的是否为s分配空间,初步分析结果为:不分配!文中的s只是s[0]的地址的代号而已。

#include<stdio.h>
int main() {
    int a=3;
    char s1[] = "test";
    int b=4;
    char s2[] ="test2";
    printf("the address of a is %u\n",&a);
    printf("s1 is %u\n",s1);
    printf("the address of s1 is %u\n",&s1);
    printf("the address of b is %u\n",&b);
    printf("s2 is %u\n",s2);
    printf("the address of s2 is %u\n",&s2);
   }

输出结果:


the address of a is 3213037836
s1 is 3213037827
the address of s1 is 3213037827
the address of b is 3213037832
s2 is 3213037821
the address of s2 is 3213037821


    由结果能够看出,编译器作了些优化。

 7、其余相关经典文章转载


  王海宁,华清远见嵌入式学院讲师,对const关键字的理解

   http://***Column/Column311.htm


     目前在进行C语言补习时,发现不少的同窗对于const这个关键字的理解存在很大的误解。如今总结下对这个关键字理解上的误区,但愿在之后的编程中,可以灵活使用const这个关键字。

一、 const修饰的变量是常量仍是变量

     对于这个问题,不少同窗认为const修饰的变量是不能改变,结果就误认为该变量变成了常量。那么对于const修饰的变量该如何理解那?

下面咱们来看一个例子:

int main
        {
                char buf[4];
                const int a = 0;

        a = 10;
        }

     这个比较容易理解,编译器直接报错,缘由在于“a = 10;”这句话,对const修饰的变量,后面进行赋值操做。这好像说明了const修饰的变量是不能被修改的,那到底是不是那,那么下面咱们把这个例子修改下:

int main
        {
                char buf[4];
                const int a = 0;

        buf[4] = 97;
                printf(“the a is %d\n”,a);
        }

其中最后一句printf的目的是看下变量a的值是否改变,根据const的理解,若是const修饰的是变量是不能被修改的话,那么a的值必定不会改变,确定仍是0。可是在实际运行的结果中,咱们发现a的值已经变为97了。这说明const修饰的变量a,已经被咱们程序修改了。

那综合这两个例子,咱们来分析下,对于第二例子,修改的缘由是buf[4]的赋值操做,咱们知道buf[4]这个变量已经形成了buf这个数组变量的越界访问。buf数组的成员自己只有0,1,2,3,那么buf[4]访问的是谁那,根据局部变量的地址分配,能够知道buf[4]的地址和int a的地址是同样,那么buf[4]实际上就是访问了const int a;那么对buf[4]的修改,天然也修改了const int a的空间,这也是为何咱们在最后打印a的值的时候看到了97这个结果。

那么咱们如今能够知道了,const修饰的变量是不具有不容许修改的特性的,那么对于第一个例子的现象咱们又如何解释那。

第一个例子,错误是在程序编译的时候给出的,注意这里,这个时候并无生成可执行文件,说明const修饰的变量能否修改是由编译器来帮咱们保护了。而第二个例子里,变量的修改是在可执行程序执行的时候修改的,说明a仍是一个变量。

综上所述,咱们能够得出一个结论,那就是const修饰的变量,其实质是告诉程序员或编译器该变量为只读,若是程序员在程序中显示的修改一个只读变量,编译器会绝不留情的给出一个error。而对于因为像数组溢出,隐式修改等程序不规范书写形成的运行过程当中的修改,编译器是无能为力的,也说明const修饰的变量仍然是具有变量属性的。

二、 被const修饰的变量,会被操做系统保护,防止修改

      若是对于第一个问题,有了理解的话,那么这个问题,就很是容易知道答案了。Const修饰的变量是不会被操做系统保护的。

其缘由是操做系统只保护常量,而不会保护变量的读写。那么什么是常量?好比“hello world”这个字符串就是被称为字符串常量。

对于这个问题的另外一种证实方法,能够看下面这个程序:

int main
        {
                const int a;
                char *buf = “hello world”;

        printf(“the &a is %p, the buf is %p\n”,&a, buf);
        }

能够发现buf保存的地址是在0x08048000这个地址附近的,而a的地址是在0xbf000000这个地址附近的,而0x08048000附近的地址在咱们linux操做系统上是代码段。这也说明了常量和变量是存放在不一样区域的,天然操做系统是会保护常量的。

若是咱们知道这个道理后,再看下面的题目:

int main
        {
                char *buf = “hello”;

        buf[0] = ‘a’;
                printf(“the buf is %s\n”,buf);
        }

咱们能够思考下,这个程序的运行结果会是什么呢?

相关文章
相关标签/搜索