首页 > 系统相关 >C语言学习笔记(04)——内存空间的使用

C语言学习笔记(04)——内存空间的使用

时间:2024-07-10 11:41:48浏览次数:10  
标签:p1 04 int 内存空间 C语言 char 地址 buf 指针

指针

指针概述:内存类型资源的地址、门牌号的代名词

指针只是个概念,要用还得用指针变量:存放指针这个概念的盒子

C语言编译器对指针这个特殊的概念,有2个疑问?

1、分配一个盒子,盒子要多大?

​ 在32bit系统中,指针就是4个字节,指针大小都固定了,就是4字节,跟你指向什么类型没有关系

2、盒子里存放的地址 所指向 内存的读取方法是什么(指向这段内存的空间大小)?

char *p;	char是修饰*的,这样定义就告诉p一次只读一个字节
int *p;		int就代表p一次只读4个字节,这样就完成了声明和定义
    
int *p1;
char *p2;
printf("the p1 is %lu,the p2 is %lu\n",sizeof(p1),sizeof(p2));
打印出的结果p1、p2都是4
    
printf("the p1 is %lu,the p2 is %lu\n",sizeof(*p1),sizeof(*p2));
结果是4、1,一次读4个字节、1个字节,也可以理解为内容是4个字节、1个字节(指向这段内存的空间大小)
int a = 0x12345678;
int *p1;

p1 = 0x1122;	//这样的写法是可以的,1122这个数肯定可以放到32bit的盒子里,是可以赋值进去的
但是!赋值进去后,属性发生了改变,原来代表数字,赋值进去后代表了地址,
而1122对应的资源是谁呢,资源合不合法呢?    
所以,指针指向内存空间,一定要保证合法性,保证这个空间确实存在且能读能写
p1 = &a;	这样才正确,&a是a的门牌号
printf("the p1 is %x\n",*p1);	//the p1 is 12345678	*p是取内容




当:
int a = 0x12345678;
char *p1;    
p1 = &a;	//a的地址一定是门牌号的最小值
printf("the p1 is %x\n",*p1);	//the p1 is 78	*p是取内容

小端模式:

数据的低位放在低地址空间,数据的高位放在高地址空间

简记:小端就是低位对应低地址,高位对应高地址

大端模式:

数据的高位放在低地址空间,数据的低位放在高地址空间

简记:大端就是高位对应低地址,低位对应高地址

p、*p、&p的区别

如定义:
int a = 10;
int *p;
p = &a;

则:
 p:因为p = &a;所以就是a的地址
*p:p的内容,就是a的值
&p:p自己本身的地址

二级指针也同理:

图示如下:

指针+修饰符

const:常量、只读(不能变)

const char *p;	//const修饰char,一个字节只读方式来读取内存,更倾向于第一种写法
char const *p;


char * const p;		//const修饰p,p指向的是一个地方,不能再变了,更倾向于第一种写法
char *p const;


const char * const p;		//指向的地址不能变,内容也不能变;希望产生ROM空间时用
const char *p;	//p所指向的内存空间是只读的,p可以指向任意的只读空间,但不能对p里的内容进行操作
就可以理解为字符串,"hello world",内容是永远不变的,字符串的不可变性
看到:const char *,第一反应就是字符串
    
char * const p;	//p指向的是一个固定地址,里面的内容是可以修改的
与硬件资源相关的,如LCD显卡缓存

段错误:指针指向的内容被非法访问了

#include <stdio.h>
int main ()
{
	char *p = "hello world!\n";		//指针指向字符串时,规范写法:const char *p = "xxx"
    								//""相当于整型常量,不可变的
    printf( "the one is %x\n",*p);	//打印出68,即h的ASCII码
    *p = 'a';						//非法访问,出现段错误
	printf( "the %s\n",p);
}



	char *p = "hello world!\n";			//这段内存不可变
	char buf[] = {"hello world!\n"};	//buf是可变的,buf属于main函数的变量内存
	char *p2 = buf;
	*p2 = 'a';							//这样就没问题
	printf( "the %s\n",p2);				//the aello world!

volatile:防止优化指向内存地址,多跟硬件有关

volatile char *p;
*p == 0x10;			//这样编译器就会不断地从指向的内容去读,而不会优化掉

typedef

char *name_t;			name_t是一个指针,指向了一个char类型的内存
typedef char *name_t;	name_t是一个指针类型的别名,指向了一个char类型的内存

name_t abc;    

指针+运算符

++、--、+、-

int *p = xxx;	[0x12]
p+1				[0x12 + 1*(sizeof(*p))]

指针的加法、减法运算,实际上加的是一个单位,单位的大小可以使用sizeof(p[0])

int *p		p+1
char *p		p+1		两者的结果是不同的
    
p+1 p-1		
p+1时:p不变,以p位参考位置,向上或向下查看,只是看  
    
p++ p--:不仅仅是看内容,把p的内容都更新了
    

[]

变量名 [ n ]

n:ID号,标签

地址内容的标签访问方式

如:p[2],p为基地址的第2块内容

p+n		p+n得到的是地址
p[n]    访问的是内容,加了[]后自动会把内容取出来
两种写法都是一致的

变量分配

是从高往低分配的,栈区,从上往下

int main()
{
    int a = 0x12345678;	a在高地址
    int b = 0x99991199;	b在低地址
        
    int *p1 = &b;
    char *p2 = (char *)&b;	//加上强制类型转换,可以不报指针类型与数据类型不符合的警告
    printf("the p1+1 is %x,%x,%x\n",*(p1+1),p1[1],*p1+1);
    printf("the p2+1 is %x\n",p2[1]);
}    

char *p2 = (char *)&b;

相当于分两步写:p2=&b 两边再加上char *

类型没有变化,大小还是那个大小,只是大小的属性不一致了

p1+1只是得到了a的地址(门牌号),*(p1+1)才是取内容(gcc编译器是连续存放的),其他的不一定

*(p1+1)和p1[1]是一样的,都是内容

*p1+1 是把内容先取出来,再加1,会得到0x9999119a

p2[1]的结果是11

指针越界访问

int main()
{
    const int a = 0x12345678;	//a在高地址
    int b = 0x11223344;	//b在低地址
        
    int *p1 = &b;
    
    p[1] = 0x100;
    printf("the a is %x\n",a);	//就可以越界修改a的值
}   

逻辑操作符

指针用的最多的还是==、!=

1、跟一个特殊值进行比较 0x0:地址的无效值,结束标志

if(p==0x0),往往我们会写一个宏:

if(p==NULL),从语言编译器一边会预定义好NULL的值,

2、指针必须是同类型的比较才具有意义

char * int * 是不同种类型

多级指针

int **p	指向指针的指针
首先找到这行的关键字:p开头,然后左边有一个*,就告诉我们p是*(p是一个箭头),那么p就一定是地址
往外继续看,发现*p左边还有*,说明p一次读4个字节  
  
p里面存放的内存还是一片连续的空间,这片连续空间的限制大小是4个字节,只不过这4个字节非常特殊,还是指针,这个指针就是以int决定的来读取内存    
    
p开头,然后左边有一个*,就告诉我们p是一个箭头,这个箭头里头存什么是由第2个*决定的
说明p里面存放的还是一个箭头,这个箭头该如何操作呢,就是由int决定的    

如:char **p;

p[0] p[1] ... p[n]

当某一个p[m] == NULL时,意味着这段二维空间就结束了

多级指针就是一段连续空间,这样的连续空间保存着其他空间的地址,把不连续的空间变为连续

#include <stdio.h>

int main(int argc,char **argv)	//main函数的标准写法就是这样
{
    int i;
    for(i=0;i<argc;i++){
        printf("the argv[%d] is %s\n",i,argv[i]);
    }
    return 0;
}

gcc -o build 005.c
执行./build 
结果为:
the argv[0] is ./build
执行./build hello 123 456
结果为:
the argv[0]is ./build
the argv[1]is hello
the argv[2]is 123
the argv[3]is 456

二级指针还有一个常用的模板:

#include <stdio.h>

int main(int argc,char **argv)	//main函数的标准写法就是这样
{
    int i = 0;
    while(argv[i] != NULL){
        printf("the argv[%d] is %s\n",i,argv[i]);
        i++;
    }
    return 0;
}
执行./build hello 123 456
结果还是一样:
the argv[0]is ./build
the argv[1]is hello
the argv[2]is 123
the argv[3]is 456

数组

数据类型 数组名[m]

m的作用域是在申请的时候

int a[100]; 定义了100个int大小的元素

数组名是地址常量,指针是表示地址的变量

数组名一定不能放到=的左边 ==大忌:a === 也不可以写a++,因为a++等价于a=a+1

char buf[100];

buf = "hello world!"; 错误!!

数组名就是一个常量标签,就像函数名叫main,不管怎么运行都不变

数组的细节知识:

​ a是数组首元素的地址,就是指向首元素的指针,a等于&a[0];&a是数组首地址;a和&a值是一样的

​ a+1:就是数组首地址加上一个元素所占的地址大小,这里int是4个字节,所以加上1x4

​ &a+1:代表的是加上整个数组的大小,这里数组尺寸是3,所以+1代表的是地址加上3x4,指向数组末尾的位置

​ * (a+1):代表的是数组第一个元素的值,就是a[1]的值,不再是元素的地址

​ *(&a+1):将&a+1地址取出来

​ &a+i = a + i*sizeof(a);

​ a+i = a +i*sizeof(a[0]);

数组空间的初始化

初始化实际上就是空间的赋值问题,按照标签逐一处理:a[0] = xx,a[1] = yy,...,a[n] = zz

int a[10] = 空间;

int a[10] = {10,20,30}; {}就代表空间,是一个块

===>a[0] = 10; a[1] = 20; a[2] = 30; a[3] = 0; 后面不写的默认把0赋进去(具体看编译器,也可能是随机值)

数组空间的初始化和变量的初始化本质不同,尤其是在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助

char buf[10] = {'a','b','c','\0'};
buf当成普通内存来看,没有问题
buf当成一个字符串来看,这种赋值方式一定要在最后加上一个'\0',也就是0
    字符串的重要性,结尾一定有个'\0',内存中就是0的表现形式
    
char buf[10] = {"abc"};		是buf当成字符串空间来看最合理的写法,C语言编译器看到""会自动加\0   所以实际上这里是4个字节了
    
    
char buf[10] = "abc";	abc这个内存逐一地拷到buf中,系统会有两个abc,一个常量的abc,一个变量abc
    buf[2] = 'e';	√没问题

char *p = "abc";	用指针去指常量区域
	p[2] = 'e';		×肯定出现段错误,因为p指向常量,不能对常量进行修改
char buf[] = "abc";	 4个字节
char buf[10] = "abc";
假如我想要让:	buf = "hello world" 怎么办,当然这种写法是大错特错!!
这就是第二次内存初始化,或者说第二次赋值,只能是逐一处理
buf[0] = 'h'	buf[1] = 'e' ... buf[n] = 'd'	buf[n+1] = 0;字符串的赋值一定要注意0的赋值

strcpy、strncpy

当把一块空间当成字符空间时,C语言提供了一套字符拷贝函数

字符拷贝函数的原则:一旦空间中出现了0这个特殊值,函数就即将结束了,不管0在哪里,看到就结束了

strcpy():
char buf[10] = "abc";
//假如我想要让:	buf = "hello world"
strcpy(buf,"hello world");	但是在工程中绝对不要用此函数,因为会导致非常严重的内存泄露问题!!!!万一字符串很长,就会一直拷贝,不止10个元素了,就会出问题
应该使用 strncpy()函数
strncpy(buf,"hello world",n);	n限制了拷贝的数量    

字符空间

​ ASCII码编码来解码的空间,就是给人看的

​ 用%s来看的,比如人写abc时,在计算机内存中是用'a','b','c'来表示的,就是char类型,char类型的编码方式就是ASCII码

​ \0作为结束标志

非字符空间

​ 当做数据采集时要存储数据 0x00 - 0xff 8bit

​ 形成了一个不成文的规定:

​ char buf[10]; ---->string

​ unsigned char buf[10]; ---->data

非字符空间的拷贝三要素:1、源 2、目的地 3、个数

C语言提供了一个函数:memcpy(dest, src, n)

int buf[10];
int sensor_buf[100];

memcpy(buf,sensor_buf,10*sizeof(int));	//或者用sizeof(buf)最保险


unsigned char buf1[10];
unsigned char sensor_buf[100];

memcpy(buf1,sensor_buf,10*sizeof(unsigned char));
注意:不要用strncpy(),如果一开始就有0值,那么buf就一直为空!

指针数组

由指针构成的数组

char * a[100];

sizeof(a) = 100 * 4; //每个指针是4个字节

char **a;

看到char **a,就可以通过标签a加下标:a[0]、a[1]这样的操作去访问每一个元素,然后每一个元素里面的内容就可以访问的到

多维数组

数组名的保存

定义一个指针,指向int a[10]的首地址

int *p1 = a; 没有任何问题

定义一个指针,指向int b[5] [6]的首地址

int **p2 = b; 一次读4个字节而已,并不匹配

int (*p2)[6] = b; 这样才对!

二维数组读内存的方法是一行一行读。5行6列,一次要读6个int

int *p[5];		指针数组,好多个指针指向不同的块
int (*p)[5];	数组指针,一个指针,一次性可以读5个块,5个int的方法读内存

数组指针和指针数组的区别

指针数组是指针类型的数组,里面的元素是指针类型的数据

数组指针(行指针)是一个指针变量,似乎是C语言里专门用来指向二维数组的,它的p+1表示到下一行。

int b[2] [3] [4];

int (*p)[3] [4];

结构体

#include <stdio.h>
struct abc{
	char a;
    int b;
};
int main ()
{
	struct abc buf;
	printf ("the buf is %lu\n",sizeof(buf));
}
>>the buf is 8

为了效率,希望牺牲一点空间换取时间的效率

#include <stdio.h>
struct abc{
	char a;
    short e;
    int b;
};
struct my{
	char a;
    int b;
    short e;
};
int main ()
{
	struct abc buf;
    struct my buf1;
    
	printf ("the buf is %lu:%lu\n",sizeof(buf),sizeof(buf1));
}
>>the buf is 8:12
short是两个字节,因此第一种定义方式:1 0 1 1;1 1 1 1	只需要2个字节
    			   第二种定义方式:1 0 0 0;1 1 1 1;1 1 0 0	需要3个字节

内存分布图

编译--->汇编--->链接

#include <stdio.h>
int main()
{
	int a;
	a = 0x10;
	printf("the a is %p\n",&a);		用%p看地址会合理一些,它会自动给加上0x
    printf("the is %p\n", main);
	return 0;
}
>>the a is 0xbfcle8cc		局部变量在高内存区域
>>the is   0x80483e4		函数在低内存区域
#include <stdio.h>
int a;
int main()
{
	a = 0x10;
	printf("the a is %p\n",&a);		用%p看地址会合理一些,它会自动给加上0x
    printf("the is %p\n", main);
	return 0;
}
>>the a is 0x804a01c				
>>the is   0x80483e4		当a为全局变量时,此时两者都在低段地址上了

内存分布图:

只读空间

代码段、只读数据段、全局变量空间都是整个程序结束后才消失,生存周期最长

静态空间,C语言编译时已确定

#include <stdio.h>

int main()
{
    int a;
    unsigned char *p;
	a = 0x10;
	printf("the a is %p\n",&a);		用%p看地址会合理一些,它会自动给加上0x
    printf("the is %p\n", main);
    
    p = (unsigned char *)main;
    printf("the p[0] is %x\n",p[0]);			
    p[0] = 0x12;		//出现了段错误	
    printf("+++++++ the p[0] is %x\n",p[0]);
	return 0;
}
>>the a is 0x804a024				
>>the is   0x80483fa
>>the p[0] is 55		//读没有问题,而且是一个字节一个字节地读
>>Segmentation fault	//出现了段错误
#include <stdio.h>

int main()
{
	char *p1 = "helao world";
    
	printf("the p1 is %s\n",p1);		
    printf("the string address is %p\n", "helao world");
	p1[3] = 'l';			//出现了段错误,因此字符串不能修改
    printf("the p1 is %s\n", p1);
	return 0;
}
>>the p1 is helao world			//可以看,没问题
>>the string address is 0x80485a0
>>Segmentation fault	//出现了段错误   

''是个数,给人描述逻辑或者字符时会用到
""是一片空间,是地址的属性,不是值的属性

栈空间

运行时,函数内部使用的变量,函数一旦返回,就释放,生存周期是函数内

size命令 – 显示文件各段大小 size 文件名

text:代码段

data:已初始化地数据段

bss:未初始化的数据段

dec = text + data + bss

hex:为dec的十六进制

strings命令:把双引号里的东西打印出来 strings 文件名

nm命令:查看可执行程序的标签 nm 文件名

static修饰的就只在函数内有效,局部有效,但是存放还是在全局数据段中

堆空间

运行时,可以自由,自我管理地分配和释放地空间,生存周期由我们来决定

分配和释放必须配对

分配:

malloc()一旦成功,返回分配好的地址给我们,只需要接收即可;对于这个新地址的读法,由我们灵活把握

输入参数指定分配的大小,单位就是B(字节)。

如要申请一个int a[5]; 就要malloc(5*sizeof(int));

char *p;
p = (char *)malloc(100);
if(p == NULL){		//一般情况下需要对p为空进行判断,进行处理或者忽略,防止申请失败
    error...
}

malloc申请空间运行一次就申请一次,函数返回后,p没有了,但是p指向的空间还有

释放:

free(p); 释放malloc申请的地址,不释放就会导致内存泄露

p = NULL; 释放后应将指针设置为NULL,防止悬挂指针的出现

标签:p1,04,int,内存空间,C语言,char,地址,buf,指针
From: https://www.cnblogs.com/xishiyuyuan/p/18293734

相关文章

  • C语言—-数据的输入输出
    数据的输入C程序中实现输入的函数很多,下面逐个来进行介绍用printf函数输出数据printf函数的一般格式printf(“格式控制”,输出列表);例如#include<stdio.h>intmain(){ inta=1; printf("a=%d\n",a); printf("HelloWord"); return0;}......
  • 【C语言】学习笔记:找出一个二维数组中的最大值,并打印出该最大值及其在数组中的位置
    找出一个二维数组中的最大值,并打印出该最大值及其在数组中的位置。首先,定义了必要的变量,包括用于遍历数组的索引变量i和j,以及用于存储最大值及其位置的变量hang、lie和max。定义了一个名为arry的二维数组,并初始化了其元素。使用两个嵌套的for循环来遍历数组,并......
  • C语言学习笔记(03)——常用运算符
    基本运算符*/inta=b*10; CPU可能多个周期,甚至要利用软件的模拟方法去实现乘法inta=b+10; CPU一个周期可以处理/取整%取余一般使用/和%配合得到小数,一般/的结果得到的是整数,除非: floata,b,c,d; a=7/2; b=7.0/2; c=7/2.0; d=7.0/2.0; printf......
  • 墨烯的C语言技术栈-C语言基础-010
    十.选择语句和循环语句如果你好好学习,校招时拿一个好offer,走上人生巅峰如果你不学习,毕业等于失业,回家卖红薯这就是选择结构intmain(){intinput=0;printf("加入C语言\n");printf("要好好学习吗(1/0)?");scanf("%d",&input);//if(input==1){printf("好offe......
  • c语言函数指针和函数数组
    1.函数指针我们都知道c语言指针可以指向整形,浮点型,字符等等,但实际上指针也可以指向函数。实际上当我们定义一个函数时,最终都会创建一个函数指针存储函数的地址。那么函数指针怎么定义呢?函数指针的定义不同于其他变量的定义,其他变量的定义是这样的:变量类型变量名;但c......
  • 常用的排序算法(C语言)
    1、冒泡排序(BubbleSort)冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮......
  • Linux C++ 045-设计模式之工厂模式
    LinuxC++045-设计模式之工厂模式本节关键字:Linux、C++、设计模式、简单工厂模式、工厂方法模式、抽象工厂模式相关库函数:简单工厂模式基本简介从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫做静态工厂方法(StaticFactoryMethod)模式,但不属于23种GOF设计模......
  • 洛谷P5594 【XR-4】模拟赛C语言
    #include<stdio.h>intmain(){ intn,m,k; inti,j; inth,l; scanf("%d%d%d",&n,&m,&k); intarr[n+1][m+1]; intday[k+1]; for(i=1;i<=n;i++){//录入数据 for(j=1;j<=m;j++){ scanf("%d&quo......
  • 洛谷P1014Cantor 表 C语言
    #include<stdio.h>intmain(){intinput;inth,k;inti,sum=0;scanf("%d",&input);for(i=1;;i++){sum+=i;//求出input数在那个范围内,i就是行数,sum就是所有行加起来数的个数if(sum>=input){h=......
  • 洛谷P1308 [NOIP2011 普及组] 统计单词数C语言
    #include<stdio.h>#include<string.h>#include<ctype.h>intmain(){charcheck[11];charstr[1000001];intf_num=0;intcount=0;inti=0;intj=0;intp=1;gets(check);gets(str);......