alin的学习之路:C语言篇(一)(内存四区模型,宏函数,调用惯例,内存存储方式)

@TOC(内存四区模型,宏函数,调用惯例,内存存储方式)

1. 内存四区及其使用注意

内存四区:代码区,全局静态区,栈区,堆区

代码区

代码区存放的是CPU执行的二进制指令
特点:

  1. 只读
  2. 共享

栈区

特点:

  1. 先进后出
  2. 由编译器自动管理申请和释放
  3. 函数运行结束后,函数在栈上开辟的空间会被自动释放掉
  4. 栈上的空间较小,不适合存放大量的数据

注意事项:

  1. 不要返回局部变量的地址:
int* func()
{
	int a = 10;
	return &a;
}
void test01()
{
	int *p = func();
	printf("%d\n", *p);
}
结果:
结果已不重要,虽然输出的是10,但这是编译器对数据进行的保护,防止错误操作,实则a已被释放,p是个野指针

char* func2()
{
	char str[] = "hello world";
	return str;
}
void test02()
{
	char* p = func2();
	printf("%s\n", p);
}
结果:
返回乱码,在输出的时候str已被释放

堆区

  1. 堆区:手动申请,手动释放
  2. 基本使用方式:
int* getSpace()
{
	int *p = malloc(sizeof(int)*5);
	if (p == NULL)
	{
		printf("malloc error");
		return;
	}
	for (int i = 0; i < 5; ++i)
	{
		p[i] = 100 + i;
	}
	return p;
	
}
void test01()
{
	int* p = getSpace();
	for (int i = 0; i < 5; ++i)
	{
		printf("%d\n", p[i]);
	}
	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}
结果:
100
101
102
103
104
  1. 注意事项:

    1. 主调函数的指针没有分配内存,被调函数中用同级指针无法对其进行描述
//pp是拷贝了p的值,pp和p没什么关系
void allocateSpace1(char* pp) 
{
	char* temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "hello world");
	pp = temp; 
}
void test02() 
{
	char* p = NULL;
	allocateSpace1(p);
	printf("%s\n", p);
}
结果:
(null)
  1. 解决方法1:高级指针
//解决方法1   利用二级指针
void allocateSpace2(char** pp)
{
	char* temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "hello world");
	*pp = temp;
}
void test03()
{
	char* p = NULL;
	allocateSpace2(&p);
	printf("%s\n", p);
}
结果:
hello world
  1. 解决方法2:被调函数返回值返回地址
//解决方法2    利用返回值
char* allocateSpace3()
{
	char* temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "hello world");
	return temp;
}
void test04()
{
	char* p = NULL;
	p = allocateSpace3();
	printf("%s\n", p);
}
结果:
hello world

calloc 和 relloc的使用

calloc

void *calloc(int num_elems, int elem_size)

  1. 与malloc对比:

    1. calloc会为创建的内存赋初值0,malloc不会
    2. calloc的参数1是开辟的空间内的元素个数,参数2是开辟的空间内的元素大小
  2. 代码演示:

void test01()
{
	int* p = calloc(10, sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	for (int i = 0; i < 10; ++i)
	{
		p[i] = i+10;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	free(p);
}
结果:
100
10-19

realloc

void *realloc(void *mem_address, unsigned int newsize)

  1. 作用:在原有堆区内存的基础上再开辟一块容量为newsize大小的内存
  2. 参数:参1:指向原堆区空间的指针,参2:新的堆区大小
  3. 代码演示:
void test02()
{
	int* p = (int*)malloc(10 * sizeof(int));
	printf("%p\n", p);
	for (int i = 0; i < 10; ++i)
	{
		p[i] = i + 10;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", p[i]);
	}
	p = (int*)realloc(p, 20 * sizeof(int));
	if (p == NULL)
		return;
	printf("%p\n", p);
	for (int i = 0; i < 15; i++)
	{
		printf("%d\n", p[i]);
	}
	free(p);
}
结果:
重新开辟后的p的地址和原先的一样
  1. realloc的机制:扩容时,如果原来的空间后面有连续的空间可以进行扩容,那么则直接扩容,不修改原地址;如果原来的空间后面没有足够的内存大小,那么在其他位置开辟一个新的newsize大小的空间,并且将原来的内存中的数据拷贝到新的内存中,如此堆区的地址就发生了改变
void test03()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
		return;
	printf("%p\n", p);
	
	p = (int*)realloc(p, 200 * sizeof(int));
	if (p == NULL)
		return;
	printf("%p\n", p);
	
	free(p);
}
结果:
013B78B8
013C0828

全局变量和静态变量

  1. extern关键字修饰的变量在同一个项目下的不同文件中可以被使用
test.c
extern int gl_a = 10;

work.c
//外部可访问
void test01()
{
	extern gl_a;
	printf("%d\n", gl_a);
}
  1. 静态变量只能在本文件中使用,作用域是本文件
  2. 静态变量只初始化一次
//静态变量只初始化一次
void func_s()
{
	static int a = 10;
	a++;
	printf("%d\n", a);
}

void func()
{
	int a = 10;
	a++;
	printf("%d\n", a);
}
void test02()
{
	func_s();
	func_s();
	func_s();
	func();
	func();
	func();
}

打印结果:
11 12 13
11 11 11
  1. const关键字修饰的变量
    1. const修饰全局变量时,直接修改和间接修改都不可以
    2. const修饰局部变量时,是一个伪常量,不能直接修改,可以间接修改
//const修饰的全局变量不能修改,包括直接修改和间接修改
const int num = 10;
void test03()
{
	//num = 20;  编译器报错
	int* p = &num;
	*p = 20;      //编译器不报错,运行报错
}
//const修饰的局部变量可以修改,是一个伪常量,不能直接修改,但是可以间接修改
void test04()
{
	const int num2 = 10;
	//num2 = 20;  编译器报错
	int* p = &num2;
	*p = 20;      //编译器不报错,运行报错
	printf("%d\n", num2);
}
  1. 字符串常量
    1. 值无法被修改,并且使用char*的变量指向,不同的变量里面的地址值相同
    2. 使用char[]p 这样的变量去存字符串是一个变量,可以被修改,且内存地址不相同
//字符串常量不能修改,它的值也不能修改
void test05()
{
	char* p1 = "helloworld";
	char* p2 = "helloworld";
	char* p3 = "helloworld";
	printf("%d\n", p1);
	printf("%d\n", p2);
	printf("%d\n", p3);

	//p[0] = 'w';  报错

	char p4[] = "helloworld";    //这个可以修改
	char p5[] = "helloworld";    //地址不同,是不同的变量
	char p6[] = "helloworld";
	p4[0] = 'w';
	printf("%d\n", p4);
	printf("%d\n", p5);
	printf("%d\n", p6);
	printf("%s\n", p4);
}

结果:
17595192
17595192
17595192
11795536
11795516
11795496
welloworld

2.宏函数

  1. 宏函数在预处理阶段会替换成为宏定义所替代的内容,省去了调用函数频繁进出栈的时间,用时间换取了空间,执行效率更高。通常在函数体较简单的时候使用宏函数
  2. 注意:因为宏函数只是会替换成定义的数值,而不是替换成函数计算后的结果,所以宏函数的函数体要加括号,保证不会被运算符的优先级影响
#define ADD(x,y) ((x)+(y))

int main(void)
{
	printf("%d\n", ADD(1, 2));

	system("pause");
	return EXIT_SUCCESS;
}

3.调用惯例

函数执行流程

观察下面的代码,会产生两个问题:

  1. 参数传递是从右往左还是从左往右?
  2. 函数在栈上申请的空间是主调函数去释放还是该函数自己去释放?
int func(int a, int b)
{
	int testa = a;
	int testb = b;
	return testa + testb;
}

void test01()
{
	int ret = 0;
	ret = func(10, 20);
	printf("%d\n", ret);
}

想必上面这段代码是如何在栈上开辟空间的都应该了解,那么解决上面遗留的两个问题。
于是引入了调用惯例的概念:调用惯例解决出栈方,形参入栈顺序,名称修饰三个问题
在这里描述C语言的调用惯例:cdecl

  1. 参数传递:从右往左
  2. 出栈方:函数调用方
  3. 名称修饰:_函数名

4.栈的生长方向

内存四区小红的栈存储数据时栈底在高地址,栈底是在低地值,用一个demo来验证

void test01()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	printf("%d\n", &a);
	printf("%d\n", &b);
	printf("%d\n", &c);
	printf("%d\n", &d);

}
结果为:
7338552
7338540
7338528
7338516

观察到从a到d他们的地址是递减的,那么也就得出结论栈底在高地址,栈顶在低地值

5.内存存储方式

我们已经知道了栈的生长方向是栈底在高地址,栈顶在低地值,那么在栈中存放数据的话,低位在高地址还是在低地值呢
用一个程序来解答:

void test02()
{
	int a = 0x11223344;
	char* p = &a;
	printf("%x\n", *p);
	printf("%x\n", *(p+1));
	printf("%x\n", *(p+2));
	printf("%x\n", *(p+3));
}
结果:
44
33
22
11
p指向低位44,p+1指向33,说明44存在低地值,33存在高地址

结论:

  1. 在内存四区中的栈上存放数据的话,位存放在地值,位存放在地址
  2. 上述存放方法称为小端模式:常用于家用电器
  3. 与小端模式相反的是大端模式:位存放在地址,位存放在地值,常用于大型服务器和大型设备
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值