精通到熟悉C系列1——存储类型&预处理&地址对齐&特殊函数

8、内存映像&内存管理&存储类型

静态的意思是该变量在内存中原地不动。

8.1、存储类型

自动存储类型

寄存器存储类型

全局非static静态存储类型

全局static静态存储类型

局部static静态存储类型

外部存储类型

8.1.1、作用域

块作用域

定义在块中的变量具有块作用域。

函数作用域

即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数。

函数原型作用域

从形参定义处到原型声明结束。

文件作用域(也称全局变量)

从它的定义处到该定义所在文件的末尾可见。

8.1.2、链接

外部链接

内部链接

无链接

具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。

具有文件作用域的变量可以是外部链接或内部链接。

8.1.3、存储期

静态存储期

在程序执行期间一直存在。

线程存储期

从声明时到线程结束一直存在。

自动存储期

动态分配存储期

存储类别存储期作用域链接声明方式
自动自动块内
寄存器自动块内,使用关键字register
静态外部链接静态文件外部所有函数外
静态内部链接静态文化内部所以函数外,使用关键字static
静态无链接静态块内,使用关键字static

8.2、内存映像

在运行一个程序时,计算机会给当前的程序映射一个4GB大小的虚拟内存空间,程序在这样的一个虚拟内存中运行,这个虚拟内存称为内存的映像。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0rkwEkZN-1618146266417)(pic/内存映射.png)]

8.3、内存管理(堆空间的申请和释放)

//堆和栈的区别

栈:
	1》有名空间,可以通过空间的名称访问空间中的数据
	2》空间比较小
	3》分配和释放由系统自动完成,对于程序员来说,不可控制。
	4》执行效率较高

堆:
	1》无名空间,只能通过指针访问堆空间
	2》空间较大
	3》分配和释放由程序员完成,所有可以在程序中灵活的控制空间的生存期
	4》执行效率较低

8.3.1、申请堆中的空间——malloc()

#include <stdlib.h>
void *malloc(size_t size);
//参数-----size:要申请的空间大小
//返回值-----成功:申请的空间的地址,失败:返回:NULL

//注意:

申请的空间是连续的

申请的空间没有初始化

malloc返回值的地址必须要强制类型转换

需要判断返回值是否可以使用

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

int main(void)
{
	int* p;
	int i;

	p = (int*)malloc(5*sizeof(int));
	if(p == NULL){
		perror("malloc");  //打印某个函数出错的信息
		exit(1);  //结束当前程序
	}   

	for(i = 0; i < 5 ;i++)
		*(p+i) = i+1;

	for(i = 0; i < 5 ;i++)
		printf("%d\t",p[i]);
	printf("\n");

	return 0;
}

8.3.2、释放堆空间——free()

#include <stdlib.h>
void free(void *ptr);
//参数——ptr:malloc返回的地址

//注意:

不能重复释放同一块空间

不能释放申请的空间的一部分

不能给free传NULL,没有意义

举例:

int main(void)
{
	int* p;

	p = (int*)malloc(5*sizeof(int));
	if(p == NULL){
		perror("malloc");  //打印某个函数出错的信息
		exit(1);  //结束当前程序
	}   

	*p = 123;
	printf("*p = %d\n",*p);

	//p = p+1;  //不能释放申请的空间中的一部分
	free(p);
	//free(p);  //不能重复释放同一块内存空间
	free(NULL);  //没有意义

	return 0;
}
//另外两个申请堆空间的函数----不太常用
void *calloc(size_t nmemb, size_t size);  //与malloc类似,申请对空间
//参数1 ----- nmemb:申请的数据的个数
//参数2 ----- size: 申请的数据的大小
//返回值----与malloc相同 

例如: 在堆中申请有5个元素的int型的数组空间

int *p;
p = (int*)calloc(5,sizeof(int));
void *realloc(void *ptr, size_t size);   //对已申请的堆空间进行大小进行修改(扩展)
//参数1 ------要修改的对空间的起始地址
//参数2 ------修改之后的堆空间的总大小
//返回值 -----成功:修改后的空间的起始地址,失败:NULL

比如:使用malloc申请了20个字节的空间,发现不够用,此时,可以使用realloc()堆空间进行扩展

int *p; 
p = (int*)malloc(5*sizeof(int));   //申请20个字节的空间

for(i = 0; i < 5; i++)
	p[i] = i+1;

//对上面申请的空间进行扩展
p = (int*)realloc(p,10*sizeof(int));   //扩展为40个字节的空间

9、预处理&高级声明

9.1、预处理

预处理指的是编译器在编译程序之前对源程序处理的过程。

9.1.1、去掉程序中的注释
9.1.2、头文件包含**#include**
#includde <stdio.h>			//查找系统目录
#include "hot.h"			//查找当前工作目录
#include "/usr/biff/p.h"	//查找/usr/biff目录
9.1.3、宏替换**#define**
//不带参宏
#define FOUR 2*2
//带参宏
#define SQUARE(X) X*X
#define PSQR(X) printf("The square of X is %d.\n",((X)*(X)))
9.1.4、条件编译

根据条件选择性的编译程序中的某一部分代码。

第一种形式

#ifdef
	代码段A
#else
	代码段B
#endif

用于测试代码中。

#ifdef DEBUG			//通过定义这个宏,控制下面的测试代码是否被编译
int main(void)
{
	float a;
	int n;

	printf("请输入浮点数和指数:");
	scanf("%f%d",&a,&n);

	printf("%f\n",my_power(a,n));

}
#endif

第二种形式

#ifndef//如果没有定义宏,则编译代码段A,否则,编译代码段B
	代码段A
#else
	代码段B 
#endif

用于头文件中 —作用是:防止头文件被重复包含

第三种形式

#if   表达式        //如果表达式为真,则编译代码段A,否则,编译代码段B
	代码段A
#else
	代码段B 
#endif

用于注释中

int main(void)
{
#if 0
	printf("hello world\n");
#else
	printf("farsight\n");
#endif

	return 0;
}

9.2、高级声明

9.2.1、extern

extern int a;

9.2.2、typedef关键字

//给一个类型定义一个新的名称。

typedef int a;				//a是int的别名
typedef void (*p)(void)	;	//p是void(*)(void)的别名

10、字节序&地址对齐

10.1、字节序

10.1.1、概念

字节序指的是处理器在对字(大于一个字节的数据)取值时,解释其中各个字节的顺序。

10.1.2、大端序和小端序

大端序: 低字节数据保存在高地址位置,其他字节数据依次保存在低地址位置,这种字节序称为大端序。
**小端序:**低字节数据保存在低地址位置,其他字节数据依次保存在高地址位置,这种字节序称为小端序。

10.1.3、如何判断当前的机器属于哪种字节序?

int main(void)
{
	unsigned int a = 0x12345678;
	unsigned char b;

	b = *(unsigned char*)&a;

	if(b == 0x78)
		printf("小端序\n");
	else
		printf("大端序\n");

	return 0;
}		

10.2、地址对齐

10.2.1、概念

为了提高机器访问内存中的数据的效率,在给数据在内存中分配空间时,将数据的空间分配在某些特殊的地址位置。这种分配空间的方式成为地址对齐。

10.2.2、自然对齐

如果某个数据的地址能够被它的长度整除,则这种分配内存的方式称为自然对齐。

10.2.3、适当对齐

数据的M值 ----每一个数据都存在一个M值

如果数据的长度 < 机器字长,M值取数据的长度

如果数据的长度 >= 机器字长,M值取机器字长

适当对齐:如果某个数据的地址能够被它的M值整除,则这种分配内存的方式称为适当对齐。

11、特殊函数(递归、变参、回调、内联)

11.1、递归函数

如果一个函数中,调用了自身,则这样的函数称为递归函数,例如:

void fun(void )
{
	printf("hello world\n");
	fun();
}

一个正确的递归函数,必须具备以下几个条件:递推阶段,回归阶段 ,递推终止条件

所以上面的递归函数可以修改为:

void fun(int n)
{
	printf("hello world\n");
	if(n > 0)  //递推终止条件
		fun(n-1);
}

int main(void)
{
	fun(5);
	return 0;
}

11.2、变参函数

调用函数时,如果可以传递不同类型或者不同个数的参数,这样的函数称为变参函数
变参函数的函数头中,变参用…表示 ,如:

int fun(int n,...)
{
	
}
//最典型的变参函数就是:printf
int printf(const char *format, ...);

11.3、回调函数

把间接调用的函数称为回调函数,例如:

void fun(void)  //回调函数
{
	printf("hello world\n");
}

void fun1(void(*p)(void))
{
	p();  //在fun1中间接调用fun,此时fun就是回调函数
}

int main(void)
{
	fun1(fun);
	return 0;
}	

11.4、内联函数

定义函数时,在返回值类型的前面如果加一个关键字: inline ,此时该函数称为内联函数。

把一个函数定义为内联函数,就是要求系统尽可能快的调用该内联函数。(谁调用我,我就把自己所有的代码复制过去执行。)

此时,内联函数的调用过程就会与普通函数的调用过程不同,它就会以带参宏的调用过程被系统调用。

内联函数原则:

  • 函数体中的代码不易过多。
  • 函数体中不能写循环语句。
  • 一般为系统中比较重要的代码,希望它能够快速的执行。
  • 内联函数一般会定义在头文件中。

【附录】

12、头文件

assert.h

ctype.h

errno.h

float.h

limits.h

locale.h

math.h

setjmp.h

signal.h

stdarg.h

stddef.h

stdio.h

stdlib.h

string.h

time.h

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值