首页 > 其他分享 >高级C语言2

高级C语言2

时间:2024-05-05 20:01:53浏览次数:24  
标签:变量 int 高级 C语言 num 内存 NULL 指针

计算机的内存长什么样子?

1、计算机中的内存就像一叠非常厚的“便签”,一张便签就相当于一个字节的内存,一个字节有8个二进制位

2、每一张“便签”都有自然排序的一个编号,计算机是根据便签的编号来访问、使用"便签"

3、CPU会有若干个金手指,每根金手指能感知高低电平,高电平转换成1,低电平转换成0,我们常说的32位CPU指的是CPU有32个金手指用于感知电平,并计算出“便签”的编号

便签的最小编号:
00000000 00000000 00000000 00000000 = 0
便签的最大编号:
11111111 11111111 11111111 11111111 = ‭4294967295‬
所以32位CPU最多能使用4Gb的内存

4、便签的编号就是内存的地址,是一种无符号的整数类型

什么是指针:

1、指针(pointer)是一种特殊的数据类型,使用它可以用于定义指针变量,简称指针

2、指针变量中存储的是内存的地址,是一种无符号的整数类型,

3、通过指针变量中记录的内存地址,我们可以读取对应的内存中所存储的数据、也可以向该内存写入数据

4、可以通过 %p 显示指针变量中存储的地址编号

如何使用指针:

定义指针变量

类型* 指针变量名;

int num;
char n;
double d;
int* nump;	//	访问4字节
char* p; 	//	访问1字节
double* doublep;	//	访问8字节
long* lp;	//	访问4/8字节

1、一个指针变量冲只记录内存中某一个字节的地址,我们把它当做一块内存的首地址,当使用指针变量去访问内存时具体连续访问多少个字节,指针变量的类型来决定。

2、普通变量与指针变量的用法上有很大区别,为了避免混用,所以指针变量一般以p结尾,以示区分

3、指针变量不能连续定义,一个*只能定义一个指针变量

int n1,n2,n3;	//	n1 n2 n3都是int
int* p1,p2,p3;	//	int *p1,p2,p3  p1是int*  p2 p3是int
int* p1,*p2,*p3; //	p1 p2 p3都是int*

4、指针变量与普通一样,默认值是随机的(野指针),为了安全尽量给指针变量初始化,如果不知道该初始化为多少,可以先初始化为NULL(空指针)

int* p;	//	野指针
int* p = NULL;	//	空指针
给指针变量赋值:

指针变量 = 内存地址

所谓的给指针变量赋值,其实就是往指针变量中存储一个内存地址,如果该内存地址是非法的,当使用该指针变量去访问内存时会出现 段错误

//	存储堆内存地址
int* p = malloc(4);

//	存储指向num所在内存地址(stack\data\bss)
int num;	//	stack
int* p = #
注意:num变量的类型必须与p类型相同
指针变量解引用:

*指针变量名;

给指针变量赋值就是让指针指向某一个内存,对指针变量解引用就是根据指针变量中存储的内存编号,去访问该内存,具体连续访问多少个字节由指针变量定义时的类型决定

 int num = 100;

    //  定义指针变量
    int* p = NULL;

    //  给指针变量赋值
    p = #

    //  查看指针变量的值
    printf("%p\n",p);                                        
    
    //  对指针变量解引用
    printf("%d\n",*p + 10);
    *p = 88; 
    printf("%d\n",num);

如果指针变量中存储的是非法的内存地址,当程序运行到该指针变量解引用时,会出现段错误

    //  定义指针变量
    int* p = NULL;
	
	*p = 100;	//	非法访问内存 会段错误
验证指针变量中存储的就是一个整数
#include <stdio.h>                                           
void func(unsigned long addr) {
    *(int*)addr = 88; 
}
int main() {
    int num = 10; 

    func(&num);

    printf("num=%d\n",num);
}

为什么要使用指针:
1、函数之间需要共享变量

​ 函数之间的命名空间是相互独立,并且是以赋值的方式进行单向值传递,所以无法通过普通类型形参传参来解决共享变量的问题

​ 全局变量虽然可以在函数之间共享,但是过多地使用全局变量容易造成命名冲突和内存浪费

​ 使用数组是可以共享,但是需要额外传递长度

​ 因此,虽然函数之间的命名空间是相互独立的,但是所使用的是同一条内存,也就是说内存空间是同一个,所以使用指针可以解决函数之间共享变量的问题

#include <stdio.h>
void func(int* p) {
    printf("func p=%p,*p=%d\n",p,*p);
    *p = 88; 
    printf("func p=%p,*p=%d\n",p,*p);
}
int main() {
    int num = 66; 
    func(&num);
    printf("main &num=%p ",&num);                            
    printf("main:%d\n",num);
}

当函数需要返回两个以上的数据时,光靠返回值满足不了,可以通过指针共享一个变量,借助该输出型参数,返回多个数据

//  put_p输出型参数
int func(int* put_p) {
    *put_p = 20; 
    return 10; 
}

int main() {
    int num = 0;
    int ret1 = func(&num);
    printf("ret1 = %d ret2=%d\n", ret1, num);                  
}
2、使用指针可以提高函数之间的传参效率

​ 一个指针变量占内存 4 | 8 字节

​ 函数之间传参是以内存拷贝的方式进行,当参数的内存字节数比较大(大于4字节时)的时候,传参的效率就会比较低下,此时使用指针传参可以提高传参效率

#include <stdio.h>                                           
void func(long double* f) {
}
int main() {
    long double f = 3.14;
    for (int i = 0; i < 1000000000; ++i) {   
        func(&f);
    }   
}

3、使用堆内存时,必须与指针变量配合

​ 堆内存无法像栈、数据段、bss段那样给内存取名字,通过标准库、操作系统提供的管理堆内存的接口函数,来操作堆内存时,是直接返回堆内存的地址给调用者,因此必须使用指针变量配合才能访问堆内存

malloc 
realloc
calloc

学习建议:指针就是一种工具,目的是完成任务,而使用指针是有危险性,所以除了以上三种情况需要使用指针以外,不要轻易使用指针

使用指针需要注意的问题:

空指针:

​ 指针变量中存储的NULL,那么它就是空指针

​ 操作系统规定程序不能访问NULL指向的内存,只要访问必定段错误

​ 当函数的返回值是指针类型时,函数执行出错时一般返回NULL,作为函数的错误标志

​ NULL也可以作为初始值给指针变量初始化

#include <stdio.h>
int* func(void) {
    return NULL;    	//表示执行出错                                  
}
int main() {
    int* p = NULL;
    int num= 10;
    p = &num;
    printf("%d\n,",*p);	//	必定段错误
}

如何避免空指针产生的段错误?

对来历不明的指针进行解引用前先判断是否是空指针

1、当自己写的函数的参数中有指针类型时,在使用该参数时,需要先判断是否是空指针再使用

2、当使用别人提供的函数时,它的返回值类型是指针类型时,获取返回值后,也需要先判断是否是空指针再使用

int* p = malloc(4);
if(NULL == p) {
    printf("内存申请失败\n");
} else {
    *p = 100;
}
if(NULL == p) // 正确写法
if(p == NULL)	// 容易漏写= 变成赋值 错误写法
if(!p) {	//	绝大多数系统中 NULL 是0,少数系统中是1
    //	通用性不够强
}
注意:必须导入 stdio.h 后 NULL才可以使用
野指针:

​ 指针变量中存储的地址,无法确定是哪个地址、是否是合法地址,此时该指针就称为野指针

对野指针解引用的后果:

​ 1、一切正常,刚好指针变量中存储的是空闲且合法的地址

​ 2、段错误,刚好指针变量中存储的是非法的地址

​ 3、脏数据,存储的是其它变量的地址

野指针比空指针的危害性更大

​ 1、空指针可以通过if(NULL==p)判断出来,但是野指针一旦产生,无法通过代码判断,只能通过经验人为判断

​ 2、野指针就算暂时不暴露问题,不代表没有问题,后期可能随时暴露

如何避免产生野指针:

​ 所有的野指针都是人为造成的,因此想要避免野指针的危害,只能通过不人为制造野指针

​ 1、定义指针变量时一定初始化

​ 2、函数不要返回局部变量、块变量的地址,因为当函数执行结束后,该地址指向的内存就会被自动销毁回收,如果非要接收,就接受到了一个野指针

​ 3、与堆内存配合的指针,当堆内存手动释放后,该指针要及时置空

int* p = malloc(4);
*p = 100;
free(p);
p = NULL;
if(NULL==p)

标签:变量,int,高级,C语言,num,内存,NULL,指针
From: https://www.cnblogs.com/sleeeeeping/p/18173795

相关文章

  • 高级C语言1
    一、程序的内存分段:(进程映像)​ 当执行程序的运行命令后,操作系统会给程序分配它所需要的内存,并划分成以下内存段供程序使用:text代码段:​ C代码被翻译成二进制指令后存储在可执行文件中,当可执行文件被操作系统执行时,它会把里面的二进制指令(编译后的代码)加载到这个内存段,它里面......
  • 06. C语言指针
    【指针】C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:1.统一数据的调用方式,因为指针是调用数据的中间......
  • 标准C语言5
    进制转换:​ 现在的CPU只能识别高低两种电流,只能对二进制数据进行计算。​ 二进制数据虽然可以直接CPU计算识别,但不方便书写、记录,把二进制数据转换成八进制是为了方便记录在文档中。​随着CPU的不断发展位数不断增加,由早期的8位逐渐发展成现在的64位,因此八进制就不能满......
  • 标准C语言4
    一、函数什么是函数:function​函数就是一段具有某一项功能的代码集合,它是C语言中管理代码的最小单位,把具有某项功能的若干行代码封装在函数中方便管理代码且方便重复调用。函数的分类:标准库函数:​ C语言标准委员会为C语言以函数形式提供了一些基础功能,这些函数被封装在li......
  • 标准C语言3
    一、数组什么是数组:​ 数组就是变量的组合,是一种批量定义变量的方式如何定义数组:类型名数组名[数量]; intarr[8];// 相当于定义了8个int类型的变量 inta1,a2,a3,...;访问数组中的变量:数组名[下标]; 下标从0开始,范围0~数量-1遍历数组:与for循环配合,使用循环变量作......
  • 对C语言符号的一些冷门知识运用的剖析和总结
    符号目录符号注释奇怪的注释C风格的注释无法嵌套一些特殊的注释注释的规则建议反斜杠'\'反斜杠有续行的作用,但要注意续行后不能添加空格回车也能起到换行的作用,那续行符的意义在哪?反斜杠的转义功能单引号和双引号字面值,字符串,字符,字符变量的大小为什么sizeof('1')的大小是4?c......
  • 标准C语言2
    二、常量(了解)​ 常量就是程序运行过程中不能改变的量,C语言中常量有:字面值常量、宏常量、枚举常量。字面值常量100 int100l long100ll longlong100u unsignedint100lu unsignedlong100llu unsignedlonglong定义一个宏常量表示100年总共有多少秒,不考虑闰平年 #defin......
  • C语言 子进程段错误后变成僵尸进程
    空指针获取首元素时出现段错误,子进程异常退出,父进程没有处理。#include<stdio.h>#include<unistd.h>intmain(){pid_tpid;pid=fork();if(pid>0){printf("fatherprocessisPID:%d\n",getpid());while(1){......
  • 标准C语言1
    一、C语言介绍​ 丹尼斯.里奇和肯.汤普逊在1971~1973年美国贝尔实验室,在开发UNIX操作系统时,在BCPL语言的基础上(newB语言),发明第一款高级编程语言,取BCPL第二个字母作为名字,所以叫C语言​ BCPL->newB->C->UNIX->Minix->Linux​ 它是为了开发操作系统而研发的一款编程语言,它特......
  • C语言 父子进程不能共享全局变量
    父子进程不能共享全局变量。父子进程中的任何一方修改了全局变量,只是修改了副本,只对自己可见,对另一方不可见。C语言中即使加了static也不行。#include<stdio.h>#include<unistd.h>//初始值是0intflag;intmain(){pid_tpid;//父进程和子进程执行相同代码即......