欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C/C++中的const--常量指针与指针常量

程序员文章站 2022-03-25 20:36:45
...

问题解析


常量指针


常量指针是指向常量的指针,指针指向的内存地址的内容是不可修改的。
指针指向了一个常量,但是指针本身是一个变量

定义const int *p=&a;

这条语句告诉编译器,*p是常量,不能将*p作为左值进行操作。但这里的指针p还是一个变量,它的内容存放常量的地址,所以先声明常量指针再初始化是允许的,指针也是允许修改的

示例

int a = 0,b = 1;
const int *p;    //  声明常量指针p
p=&a;            //  p指向a
p=&b;            //  修改指针的值p让其指向b,允许
*p=2;            //  修改指针所指向的变量的值,不允许
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

指针常量


指针常量是指针的常量,它是不可改变地址的指针,但可以对它所指向的内容进行修改。
指针本身是一个常量,但是指着指向一个变量

指针常量定义int * const p=&a;

告诉编译器,p是常量,不能作为左值进行操作,但允许修改其指向的内容,即*p是可修改的。指针常量必须在声明的同时对其初始化,不允许先声明一个指针常量随后再对其赋值,这和声明一般的常量是一样的

示例

int a = 0,b = 1;
int *const p1 = &a; 
int *const p2;           //  不允许,必须常量定义时必须对其初始化
p2 = &b;                 //  不允许,p2是指向a的常量, 不允许作为左值
*p1 = 2;                 //  允许,  指针指向的是一个变量, 可以修改指针*p1的值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

指向常量的指针常量


如其名,不仅仅指针本身是一个常量(不可以修改其指向,指针指向的也是一个常量)
定义为const int * const p = &ca;

总结


依据右左法则右左法则:首先从最里面未定义的标识符看起,然后先往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。(右左法则不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的)
当然我们也可以依据编译原理的思想,来分析const修饰的语义

指针常量

定义 分析
char * const cp 指针常量
右左法则 cp is a const pointer to character
语义 are neat

常量指针

定义 分析
const char * p; 常量指针
char const * p C++标准规定,const关键字放在类型或变量名之前等价的,C++里面没有const*的运算符
右左法则 p is a pointer to const character;
语义 const修饰的char *, 说明该指针指向的变量是一个常量

const进阶


指针常量和常量指针我们已经明白是怎么回事了,当然我们今天说的不仅仅是这些,因为我们还有一个神乎其神的东西:编译器。语言也好,编译器也罢,语言的特性,往往需要强大编译器的支持,而编译器往往会在我们的代码中进行进一步的优化,前面提到的RVO-编译器返回值优化 就是这么回事,那么我们通过一个示例来看看,指针常量与常量指针更奇特的地方。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//  依据右左法则
//
//  char * const cp; ( * 读成 pointer to )
//  cp is a const pointer to character
//  此时, const修饰指针变量cp,说明该指针是一个常量
//
//  const char * p;
//  p is a pointer to const character;
//  此时, const修饰的char *, 说明该指针指向的变量是一个常量
//
//  char const * p;
//  同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。
//  C++标准规定,const关键字放在类型或变量名之前等价的。

// main function
int main(void)
{
    char *buf = "hello world";
    printf("栈区域地址%p  常量区%p\n\n", &buf, buf);

        //  字符串数组
    const char const str1[] = "abcd";
    const char const str2[] = "abcd";
    printf("[%p == %p] %d\n", &str1, &str2, &str1 == &str2);  //  栈区
    printf("[%p == %p] %d\n\n", str1, str2, str1 == str2);    // 栈区
    //strcpy(str1, "hello");
    printf("%s\n", str1);


    //  指向常量的指针, 指针本身的地址&p在栈中, 指向的地址p在常量区...
    const char * pstr1 = "abcd";
    const char * pstr2 = "abcd";
    printf("[%p == %p] %d\n", &pstr1, &pstr2, &pstr1 == &pstr2);    //  指针本身的地址在栈区
    printf("[%p == %p] %d\n\n", pstr1, pstr2, pstr1 == pstr2);      //  指针指向的地址在常量区
    //  指向常量的指针, 指针本身的指向可以修改
    pstr1 = "1234";
    pstr2 = "1234";
    printf("[%p == %p] %d\n", &pstr1, &pstr2, &pstr1 == &pstr2);    //
    printf("[%p == %p] %d\n\n", pstr1, pstr2, pstr1 == pstr2);

    pstr1 = "abcd";
    pstr2 = "1234";
    printf("[%p == %p] %d\n", &pstr1, &pstr2, &pstr1 == &pstr2);
    printf("[%p == %p] %d\n\n", pstr1, pstr2, pstr1 == pstr2);

    // 指针常量
    char * const pstr3 = "abcd";
    char * const pstr4 = "abcd";
    //pstr4 = "bcedf";          //  error, 此时指针的指向无法修改
    printf("[%p == %p] %d\n", &pstr3, &pstr4, &pstr3 == &pstr4);    // 指针作为局部常量存储在栈中 
    printf("[%p == %p] %d\n\n", pstr3, pstr4, pstr3 == pstr4);      // 字符串存储在常量区


    return EXIT_SUCCESS;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

运行结果


tcc的运行结果
C/C++中的const--常量指针与指针常量
gcc的运行结果
C/C++中的const--常量指针与指针常量
clang的运行结果
C/C++中的const--常量指针与指针常量

我们拿了大神法布里斯·贝拉(FabriceBellard)的tcc与现代优化技术下的编译器的编译结果进行对比,我们可以很明显的发现。
大家都有一个共通点,均把字符串本身存储在了常量区,而把局部的变量和局部的常量存储在了栈中,这点我们已经在C程序的内存布局(Memory Layout) 中已经有讨论不是我们今天讨论的重点。

我们所关注的是,一个不同支之处在于在现代优化技术下的编译器,相同的字符串变量在内存中只存储了一份, 字符数组除外,因为字符数组本身是一段连续存储的字符变量。

疑问


但是我之前上面的代码其实有点疑问,那就是char * const pstr = "abcd" 时候,我们的常理会认为pstr是一个指针常量,那么我么就应该不能修改其指向,但是可以修改其指向的字符串的值,但是结果真的如此么
我们从上面一个示例程序可以很明显的发现,pstr3和pstr4作为指针常量,指针本身的读写(主要是写)限制,是由编译器处理的,因此存在了栈区,但是字符串本身”abcd”却放在了代码段(或者说常量区)。那么我们可以大胆假设
1 指针常量本身的指向无法修改,这个由编译器在编译阶段进行检查
2 指针所指向的字符串也是无法修改的,因为字符串常量存储在代码段(或者常量区,因为有些分类中是不存在常量区的,常量区与代码段共同组成代码区)中,这段区域是由操作系统管理的只读段

#include <stdio.h>
#include <stdlib.h>


int main(void)
{
    //  The pointer itself is a constant,
    //  Point to cannot be modified,
    //  But point to a string can be modified
    char * const pstr = "abcd";  //  Pointer to a constant

    // I find that pstr(the address of "abcd") is in ReadOnly data
    // &pstr(the address of pstr) is in stack segment
    printf("%p  %p\n", pstr, &pstr);

    *(pstr + 2) = 'e';  //  segmentation fault (core dumped)
    printf("%c\n", *(pstr + 2));

    return EXIT_SUCCESS;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

于是关于这个问题,我在*上寻找到了解答segmentfault when modify char * const pstr = “abcd”;
回答的大神们都很好的回答了这个问题,把大家的答案总计一下大致说了这样一个意思
char * const pstr = “abcd” 这条语句是在定义常量时同时进行了初始化,那么初始化的变量就作为一个文本常量存储在了常量区。

我们来类比如下一条语句const int a = 10; 我们难道能够绕过a来修改变量本身的值10么,显然答案是否定的,当然和这条语句对比并不是很贴切,那么我们来看下面一条

对于 char *str = "abcd"; 很明显指针str在栈中存储,那么字符串”abcd”本身仍然放在了常量区,那么我们依照常量,是可以修改变量的值的,但是结果依旧是否定的,这个参见why do i get a segmentation fault when writing to a string initialized with char

因此请看下面代码

#include <stdio.h>
#include <stdlib.h>


int main(void)
{


    char str1[] = "abcd";
    *(str1 + 2) = 'a';   // OK
    printf("%s\n", str1);

    char *pstr1 = str1;
    *(pstr1 + 2) = 'b'; // OK
    printf("%s\n", pstr1);

    char * const cpstr1 = str1;
    *(cpstr1 + 2) = 'c';    // OK
    printf("%s\n", cpstr1);

    char *pstr2 = "abcd";
    *(pstr2 + 2) = 'd';           //  segment fault
    printf("%s", pstr2);


    char * const cpstr2 = "abcd";  //  Pointer to a constant
    *(cpstr2 + 2) = 'e';  //  segmentation fault (core dumped)
    printf("%s\n", cpstr2);

    return EXIT_SUCCESS;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

因此下面的代码无论const存在与否,都是在修改常量区的代码

char * /* const */ ptr = "abcd";
*(ptr+2) = 'e';
  • 1
  • 2
  • 1
  • 2

必然导致访存错误,引起segmentfault
如果你非要这样做的话请使用

char ptr[] = "abcd";
*(ptr+2) = 'e';
  • 1
  • 2
  • 1
  • 2

或者更复杂点

char str[] = "abcd";
char * /* const */ cpstr = str;
*(cpstr + 2) = 'a';    // OK
printf("%s\n", cpstr);


转载:http://blog.csdn.net/gatieme/article/details/48896847