C数组和指针,从此不再困惑。

目录

一、思考下图中的问题?

二、上图中问题答案,如下图:

三、分析问题答案

1、分析前知识准备

1.0、内存区术语

1.1、为什么定义数组类型变量时要指定大小或初始化

1.2、指针指向的内存一定是在堆区吗?

1.3、const关键字的思考

1.4、进程虚拟地址空间

1.5、为什么会段错误?

2、开始分析

四、总结

五、参考书籍

六、源码


一、思考下图中的问题?

二、上图中问题答案,如下图:

三、分析问题答案

1、分析前知识准备
1.0、内存区术语

1)本文内存区指的是虚拟内存。

比如:连续内存区指的是虚拟内存连续,物理内存不一定连续。

物理内存是否连续取决于操作系统建立分页映射时是否连续。

1.1、为什么定义数组类型变量时要指定大小或初始化

1)为变量分配内存是在编译时需要确定的。

变量代表的是内存地址,变量的类型确定了内存地址的范围,即从该内存地址访问多少字节的内存。

2)定义变量就是要在编译时分配固定大小的内存区并把首地址赋值给变量。

3)数组是一批固定数量的相同数据类型元素的集合。

数组内存大小=元素数据类型占用字节数 * 元素个数。

结论:定义数组变量要指定元素个数或者通过初始化方式由编译器推断元素个数。

示例:

1、char name[] = "zhangSan";  // 变量name,可以定义。

// 它是由编译器根据等号右侧的字符串常量推断出元素个数,可以确定内存大小。

2、char name2[20];  // 变量name2,可以定义。

// 方括号指定了元素个数可以确定内存大小,只是这片内存未初始化属于无效数据。

3、char name3[];  // 变量name3,不可以定义,编译时报错。

// 因为编译器无法确定分配多少内存。

1.2、指针指向的内存一定是在堆区吗?

区分两个内存地址:指针变量本身内存地址;指针指向的内存地址(即,变量存储的值)。

1)变量本身内存地址:是在编译期确定并分配的,它可能在栈区、全局变量区。取决于变量在哪定义的。

2)指针指向的内存地址:可以是栈区、堆区、全局变量区、全局常量区。取决于指针变量的赋值对象所在区。

char *pname = malloc(sizeof(char) * 5);   // pname指向的内存是堆区。

char name[5];                                           // name可以是局部变量、全局变量

char *pname2 = &name;                          // pname2指向的内存是name所在的内存。

切记:指针指向的内存是堆区时才可以free(ptr);

1.3、const关键字的思考

说三点关键的:

1)const修饰的变量,不能通过该变量进行内存的写操作。这个是由编译器在编译阶段进行检查。

2)const修饰的变量,会影响该变量要存储到哪个内存区。

1)如果要修饰的变量在栈区,加上const之后保持不变还在栈区。

2)如果要修饰的变量在全局变量区,加上const之后会挪到全局常量区。

3)const修饰的变量不代表变量所在内存不可以修改。

1)const修饰的变量内存,只是不可以通过变量本身进行修改。因为编译阶段会检查。

2)其他非const变量(一般是指针变量)拿到了const变量的地址,并进行了修改操作,在编译阶段是可以通过的。到了运行阶段至于能不能修改const变量的内存内容是由const变量所在内存区的读写权限所决定的。若内存可读写则可以修改;若内存只读(全局常量区)试图修改则会报段错误。

1.4、进程虚拟地址空间

建议阅读本文推荐的参考书籍(第六章可执行文件的装载与进程、第10章内存)

关注两点:

1)可执行文件各个section合并时,如何受内存读写权限的影响。

2)各个VMA是怎样建立的。

1.5、为什么会段错误?

访问内存时超出了内存的访问权限。

一般是指针在试图修改只读内存的数据,比如指针指向了全局常量区,通过指针进行了修改操作。

2、开始分析

以下问题分析序号对应思考图中的问题序号。

问题1:

局部变量name是非const变量可以修改其值。

问题2:

局部变量cname是const变量,通过变量本身进行修改,在编译阶段会报错。

问题3:

指针变量pname是普通字符指针变量,通过pname修改其指向的内存值在编译阶段是可以通过的。只要指针指向的内存是可读写的,运行时就可以修改。

由于pname指向的变量name属于栈区是内存可读写的,所以可以修改其值。修改后*pname和name的值一样,因为他们访问的是同片内存。

问题4:

答案及其原理和问题3一样。

虽然cname变量本身是const但它所在的内存是可读写的,可以通过其他变量修改。

问题5:

答案是可编译通过,运行时报段错误。

因为pname3是普通字符指针变量,它可以进行修改操作,编译时没有问题。

但是pname3指向的内存是全局常量区,全局常量区是只读区,所以在运行时试图修改只读内存,系统就会报段错误并终止程序运行。

问题6:

答案及其原理和问题5一样。

用字符串常量初始化指针变量,指针变量存储的值是字符串常量的地址。

四、总结

1、先弄清楚变量的地址在哪个内存区,注意内存区的可读写权限。

      若变量是指针类型,同时要弄清楚指针指向的内存是在哪个内存区。

2、const变量不能通过变量本身进行修改,编译阶段会报错。

3、const变量代表的内存不一定不可以修改,取决于内存所在区的可读写权限。

4、数组类型的变量一定是在编译期分配好内存,且内存是连续的。

      注意:使用字符串常量初始化字符类型的数组,若数组变量在栈区,则会把字符串常量拷贝到所在的栈区内存。

五、参考书籍

程序员的自我修养—链接、装载与库.pdf

六、源码

/*************************************************************************
 * @File Name: char.c
 * @Description: 
 * @Author: 少即多~
 * @Created Time: Thu 08 Aug 2024 01:07:24 AM UTC
 ************************************************************************/
#include <stdio.h>
#include <stdlib.h>

char gname[] = "zhangSan";
const char cgname[] = "zhangSan";

int main(void)
{
    char name[] = "zhangSan";
    const char cname[] = "zhangSan";

    name[1] = 'H';
    cname[1] = 'H';

    char *pname = name;
    pname[1] = 'T';

    char *pname2 = cname;
    pname2[1] = 'T';
    printf("pname2 ptr: %p, value: %s\n", pname2, pname2);

    char *pname3 = cgname;
    pname3[1] = 'T';
    printf("pname3 ptr: %p, value: %s\n", pname3, pname3);

    char *pname4 = "zhangSan";
    pname4[1] = 'T';
    printf("pname4 ptr: %p, value: %s\n", pname4, pname4);

    return 0;
}
  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值