01-内存分布

数据类型

编译器制定出的数据类型,为了更好的管理内存
![[Pasted image 20230904144626.png]]

C语言标准

ANSI 美国国家标准协会指定出的标准,在89年指定出第一套标准 C89标准

typedef使用

  1. 主要用途:给类型起别名
  2. 可以简化struct 关键字
  3. 可以区分数据类型
  4. 提高代码移植性

void使用

  1. void无类型,不能用void创建变量
  2. void用途,限定返回值类型,函数参数
  3. 限定函数中的参数列表
  4. void* 万能指针,可以不需要强制类型转换 转给其他指针赋值

sizeof操作符

// sizeof不是一个函数,是一个运算符

	// sizeof的返回值是一个unsigned int
	unsigned int a = 10;
	// 如果一个unsigned int和int运算,结果会统一转换为unsigned int
	if (sizeof(a) - 20 >0) {
		printf("大于0\n");
	} else {
		printf("小于0\n");
	}


// 当数组名传入到函数中,会退化成一个指针,指针指向数组中第一个元素的地址
void calculateArray(int array[])
{
    printf("size = %d\n", sizeof(array));
}
void test03()
{
    // 'sizeof' on array function parameter 'array' will return size of 'int*'
    int array[] = {0, 1, 2, 3, 4, 5, 6, 7};
    // 这里统计的是整个数组的长度
    printf("sizeof = %d\n", sizeof(array));
    calculateArray(array);
}

变量的修改方式

直接修改
间接修改(指针)

  1. 通过指针对内存进行修改
  2. 对自定义数据类型进行了修改
//对于自定义数据类型
struct Person
{
	char a; // 0 ~ 3
	int b;  // 4 ~ 7
	char c; // 8 ~ 11
	int d;  // 12 ~ 15
};
void test02()
{
	struct Person p1 = { 'a', 10, 'b', 20 };

	//直接修改  d 属性
	p1.d = 1000;

	//间接修改  d 属性
	struct Person * p = &p1;
	p->d = 2000;

//  p+1跳过一个struct Person,跳过16个地址
	printf("%d\n", p);
	printf("%d\n", p+1);

	char * pPerson = p;

	// 指针的类型不同,+1跳过的步长不同
	printf("d = %d\n", *(int*)(pPerson + 12));
	// 
	printf("d = %d\n",  *(int*)((int*)pPerson +3) );
}

内存分区

运行前

![[Pasted image 20230904161001.png]]

![[Pasted image 20230904161025.png]]

全局初始化数据区/静态数据区(data段)
该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和t)和常量数据(如字符串常量)。

未初始化数据区(又叫 bss 区)
存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。

代码区
共享的
只读的
数据区
已初始化的全局变量、静态变量、常量
未初始化的全局变量、静态变量、常量

运行后

程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,操作系统把物理硬盘程序load(加载)到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。

 栈区(stack)
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间

栈区注意事项 ,不要返回局部变量的地址
//栈 注意事项 ,不要返回局部变量的地址,局部变量在函数体执行完毕后会被释放,再次操作后就是非法操作,结果未知
int * func()
{
	int a = 10;
	return &a;
}

void test01()
{
	int * p = func();

	//结果已经不重要了,因为a的内存已经被释放了,我们没有权限去操作这块内存
	printf("a = %d\n", *p);
	printf("a = %d\n", *p);
}


char * getString()
{
	char str[] = "hello world";
	// 这里还是返回的栈区变量的地址
	return str;
}

void test02()
{
	char * p = NULL;
	p = getString();
	printf("%s\n", p);
}

![[Pasted image 20230904184616.png]]

 堆区(heap)
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

  • 在堆区开辟的数据,记得手动开辟,手动释放
  • 如果在主调函数中没有给指针分配内存,那么被调函数中需要利用高级指针给主调函数中指针分配内存
//注意事项
//如果主调函数中没有给指针分配内存,被调函数用同级指针是修饰不到主调函数中的指针的
void allocateSpace( char * pp )
{
	char * temp =  malloc(100);
	if (temp == NULL)
	{
		return;
	}
	memset(temp, 0, 100);
	strcpy(temp, "hello world");
	pp = temp;

}
void test02()
{
	char * p = NULL;
	allocateSpace(p);

	printf("%s\n", p);
}

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);
	free(p);
	p = NULL;
}

![[Pasted image 20231013122128.png]]

数据区

放入是静态变量、全局变量、常量
static 和 extern 区别

  1. static 静态变量:编译阶段分配内存,只能在当前文件内使用,只初始化一次
  2. extern 全局变量,C语言下默认的全局变量前都隐藏的加了该关键字
//1、静态变量
static int a = 10; //特点:只初始化一次,在编译阶段就分配内存,属于内部链接属性,只能在当前文件中使用
void test01()
{
	static int b = 20; //局部静态变量,作用域只能在当前test01中

	//a 和 b的生命周期是一样的
}

//2、全局变量
extern int g_a = 100; //在C语言下 全局变量前都隐藏加了关键字  extern,属于外部链接属性
void test02()
{
//告诉编译器 g_b是外部链接属性变量,下面在使用这个变量时候不要报错
	extern int g_b;
	printf("g_b = %d\n", g_b);

}

const修饰的变量

  1. 全局变量
    • 直接修改 失败
    • 间接修改 失败 原因放在常量区,受到保护
  2. 局部变量
    • 直接修改 失败
    • 间接修改 成功 原因放在栈上
    • 伪常量 不可以初始化数组
//1、const修饰的全局变量,即使语法通过,但是运行时候受到常量区的保护,运行失败
const int a = 10; //放在常量区
void test01()
{
	//a = 100; //直接修改 失败
	int * p = &a;
	*p = 100;
	printf("%d\n", a);

}

//2、const修饰的局部变量
void test02()
{
	const int b = 10; //分配到栈上
	//b = 100; //直接修改失败的

	//间接修改 成功, 原因是分配到栈上的,可以被修改
	//C语言下 称为伪常量
	int * p = &b;
	*p = 100;
	printf("b = %d\n", b);

	//int a[b];//伪常量是不可以初始化数组的
}

字符串常量

  • 不同的编译器可能有不同的处理方式
  • 有些编译器可以修改字符串常量,有些不可以
  • 有些编译器将相同的字符串常量看成同一个

函数调用流程

宏函数

在一定程度上会比普通函数效率高,普通函数会有入栈和出栈的时间开销

  • 将比较频繁短小的函数 写为宏函数,直接跑源码
  • 优点: 以空间换时间

调用惯例

  • 主调函数和被调函数都必须有一致的约定,才可以正确的调用函数,这个约定我们称为调用惯例
  • 调用惯例包含的内容: 出栈方、参数的传入顺序、函数名称的修饰
  • c和c++下默认的调用惯例为 cdecl
    出栈方:主调函数
    参数传递顺序:从右往左
    名称修饰:下划线+函数名

![[Pasted image 20230905124523.png]]

变量传递分析

void B()
{

}
void A()
{
	// 在函数A、B中可以使用、在main中使用不了
	int a;   
	B();
}

int main()
{
	// 在main函数和A、B中都可以使用
	int a;
	A();
}

栈的生长方向

// 栈的生长方向
void test01() {
    int a = 0;  // 栈低, 高地址
    int b = 0;
    int c = 0;
    int d = 0;  // 栈顶, 地地址

    printf("%d %d %d %d\n", &a, &b, &c, &d);
}
int main(void) {
    
    test01();
    return 0;
}

内存存储方式

小端对齐:
高位字节数据–高地址
低位字节数据–低地址

大端对齐:
高位字节数据–小地址
低位字节数据–高地址
大小端验证方式:

// 内存存储方式
void test02() {
    //      11是高位     44是地位
    int a = 0x11223344;

    char *p = (char *) &a;
    // 小端:高位字节,高地址
    printf("%x\n", *p);     // 44 低位字节  低地址
    printf("%x\n", *(p+1));
    printf("%x\n", *(p+2));
    printf("%x\n", *(p+3)); // 11 高位字节 高地址,因为p增加3,肯定比p的地址要大
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值