首页 > 其他分享 >C数组和指针,从此不再困惑。

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

时间:2024-08-08 18:59:57浏览次数:16  
标签:困惑 const 变量 name char 内存 数组 指针

目录

一、思考下图中的问题?

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

三、分析问题答案

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;
}

标签:困惑,const,变量,name,char,内存,数组,指针
From: https://blog.csdn.net/haoweicsdn/article/details/140998770

相关文章

  • 在python中将二维数组转换为彩色图像
    我有像这样的2d整数列表:list1=[[1,30,50],[21,45,9],[97,321,100]]下一步我要将其转换为numpy数组:myarr=np.asarray(list1)下一步我将使用PIL将其转换为图像,如下所示:img=Image.fromarray(myarr,"I")img.save("my.png")问题是我不想要灰......
  • 将二维数组与一维数组合并
    我目前np.append与两个数组组合,但它不能工作,它显示:ValueError:alltheinputarraydimensionsexceptfortheconcatenationaxismustmatchexactly,butalongdimension0,thearrayatindex0hassize64andthearrayatindex1hassize0这是我的......
  • 【C语言】一篇文章搞定C语言最难指针
    目录一、内存和地址(1)什么是内存的地址(2)如何寻找指定的内存地址(3)CPU和内存传递数据的方式二、指针变量和地址(1)取地址操作符(2)指针变量(3)解引用操作符(4)指针变量的大小(5)指针变量的类型的意义 ①指针的解引用②指针+/-整数③void*指针三、const修饰指针(1)cons......
  • C++ - 二级指针动态内存申请与释放
    C语言描述:#include"stdio.h"#include"stdlib.h"#include"assert.h"//二维数组内存申请int**createArray2D(introw,intclos){ int**pArray=(int**)malloc(sizeof(int*)*row); assert(pArray); for(inti=0;i<row;i++) { ......
  • 多维数组
    4.3多维数组目录4.3多维数组4.3.1二维数组一、二维数组定义方法二、二维数组的运行机制三、二维数组的使用四、多维数组之间的关系4.3.2同时定义多个数组4.3.3二维数组应用题4.3.4多维数组细节4.3.1二维数组数组不止能存储某具体值,还能用数组储存数组。介绍:我们前......
  • 数组排序算法
    4.2数组排序算法4.2.1冒泡排序冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“......
  • leetcode 718. 最长重复子数组,leetcode 1143. 最长公共子序列
    leetcode718和leetcode1143两道十分相似的题,就不放题目了思路实际上区别就在于一个要求连续数组,另一个要求不连续的序列。二者的dp表达式和状态转移其实是不一致的,前者f[i][j]代表nums1以i结尾nums2以j结尾的最长子数组长度,后者代表nums1以i结尾nums2以j结尾的区间内存......
  • C语言字符数组,字符指针,指针数组(字符串)的比较与使用
    参考文档https://blog.csdn.net/yuabcxiao/article/details/89600907 字符数组与字符指针在C语言中,可以用两种方法表示和存放字符串:(1)用字符数组存放一个字符串charstr[]="Iamhappy";(2)用字符指针指向一个字符串char*str="Iamhappy";字符数组#include<iostrea......
  • Java中一维数组的学习
    一维数组目录一维数组创建数组null数组的遍历for循环遍历数组for-each循环遍历while循环遍历do-while循环遍历数组的反向遍历创建数组Java语言使用new操作符来创建数组,语法如下:arrayRefVar=newdataType[arraySize];上面的语法语句做了两件事:使用dataType[arraySize]......
  • Java数组篇[1]:数组的定义和声明
    哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者......