(一)操作系统内存管理

1.内存分布

        这是 32‑位程序总共 4GB 的虚拟内存分布图,地址由底部 0 向上至 4G。 最上方 1G 属于操作系统内核空间,我们用户代码不能操作。往下依次为栈区、堆区、数据区、文本区。

(1)栈区默认 8M:存放局部变量、函数参数,由系统自动申请释放;

(2)堆区:需要程序员使用malloc手动开辟、free手动释放;

(3)数据区分成三块:.data 存放初始化完毕的全局、静态变量;.bss 存放未初始化全局静态变量;字符串常量区存放双引号字符串;

(4)文本区:用来储存程序代码、字面常量。

简单总结:栈系统自动管理、堆程序员手动管控,全局变量放数据段,程序代码存放在文本段。

2.堆和栈的区别

(1)栈区:

  • 栈区用来保存局部变量、函数的形参返回值、及函数调用时的保护现场
  • 栈区由操作系统自动管理
  • 栈区默认大小为8M, 比堆区空间小
  • 栈的访问效率要比堆的访问效率高

(2)堆区:

  • 堆区由开发人员手动申请和释放,使用完时及时释放,否则造成内存泄露。
  • 堆区空间比栈空间大,访问效率相对较低。

1)堆区空间使用场景

  • 在函数中开辟的空间,希望随着函数结束,该空间任然存在,可以去堆上开辟。

  • 当需要的空间比较大时,可以去堆上开辟,栈区空间有限,默认8M。

2)堆区空间使用(动态内存分配)

void *malloc(size_t size);
功能:向堆区申请内存空间
参数:
    size:申请的空间字节数
返回值:
    成功:返回申请到的空间首地址
    失败:NULL
    
void free(void *ptr);
功能:释放申请的堆区空间
参数:
   ptr:申请到的堆区的空间首地址。
     
void *calloc(size_t nmemb, size_t size);
功能:向堆区申请数组空间,并清空成0
参数:
    nmemb:申请的数组的元素个数
    size:数组中的每个元素占的字节数
返回值:
    成功:返回申请到的空间首地址
    失败:NULL

示例:

	int *p = malloc(4);
	if (NULL == p)
	{
		printf("malloc error\n");
		return 0;
	}	
	*p = 10;
	printf("*p = %d\n", *p);
	free(p);
	
	struct stu *ps = malloc(sizeof(struct stu));
	if (NULL == ps)
	{
		printf("malloc error\n");
		return 0;
	}
	//ps->name = "zhangsan";  错
	
	strcpy(ps->name, "zhangsan");
	ps->age = 10;
	ps->score = 100;
	printf("%s %d %d\n", ps->name, ps->age, ps->score);
	free(ps);
	
	char *pstr = malloc(100);
	if (NULL == pstr)
	{
		printf("malloc error\n");
		return 0;
	}
	strcpy(pstr, "hello world");	
	printf("pstr->%s\n", pstr);
	free(pstr);

3)内存泄露

  • 内存泄漏:程序运行过程中,堆区申请的空间在使用完没有释放,则造成内存泄露。
  • 避免内存泄露:堆区申请的空间使用完,及时使用free()进行释放。

动态数组:

	int *pa = malloc(ARRAY_LEN*sizeof(int));
	if (NULL == pa)
	{
		printf("malloc error\n");
		return 0;
	}
	
	for (int i = 0; i < ARRAY_LEN; ++i)
	{
		pa[i] = i;
	}

	for (int i = 0; i < ARRAY_LEN; ++i)
	{
		printf("%d ", pa[i]);
	}
	printf("\n");

(二)位运算

1.位运算符

&  按位与      全1为1,遇0则0      (&& 逻辑与)
|  按位或        全0为0,遇1则1
^  按位异或   同0异1
~  按位取反   1->0,0->1        单目运算符
<< 按位左移
>> 按位右移

示例:

15 & 3 = 3
15 | 3 = 15
15 ^ 3 = 12
~ 15 = -16           (~n = -(n+1))
<< 按位左移,低位补0    (num<<n <===> num*2^n)
>> 按位右移,有符号数高位补符号位,无符号数高位补0  (num>>n <===> num/2^n)

过程:
15:   0000 0000 0000 0000 0000 0000 0000 1111
3:     0000 0000 0000 0000 0000 0000 0000 0011

&:     0000 0000 0000 0000 0000 0000 0000 0011
|:      0000 0000 0000 0000 0000 0000 0000 1111
^:     0000 0000 0000 0000 0000 0000 0000 1100
~15:  1111 1111 1111 1111 1111 1111 1111 0000(负数补码)只看后四位
      减1:1110 1111
      取反:1001 0000
9:       0000 0000 0000 0000 0000 0000 0000 1001
9<<3: 0000 0000 0000 0000 0000 0000 0100 1000 -->72==>9*2^3
9>>3: 000 0000 0000 0000 0000 0000 0000 0000 1 -->1

2.位运算的使用

1)指定第n位置1

num = num | 1 << n;

0000 0000 ---> 0000 1000如下:
              0000 0001 << 3  -->  0000 1000
1010 1000  |  0000 1000 ==> 1010 1000

2)指定第n位清0

num = num  & ~(1 << n);

1010 1010 ---> 1010 0010
1010 1010 & 1111 0111
1111 0111 <==> ~0000 1000 <==> ~(1 << 3)
例:
0011 1100 1010 1010    对第10、11位清0
num & ~(1<<10) & ~(1<<11)

注意:这个n是从0开始数

3)两数交换

a ^ a == 0

a ^ 0 == a

a = a ^ b;
b = a ^ b;
a = a ^ b;
过程:
b = (a ^ b) ^ b = a ^ b ^ b = a ^ (b ^ B) = a ^ 0 = a;
a = a ^ b = a ^ b ^ a = 0 ^ b = b;

中间变量:

int  a,b,tmp = 0;

tmp = a;

a = b;

b = tmp;

引申:两数交换(求和方式):

a = a + b;

b = a - b;     =====>a+b-b  =  a

a = a - b;     =====>a+b-b  =   a+b-a

3.运算符的优先级

高--->低:   ~  <<  >>  &  ^  |  

(三)程序调试方法

1.打印调试

程序运行过程中打印输出:
num;
printf("num = %d\n",num);

2.gdb调试工具

gnu组织提供的Linux下的代码调试工具

- 使用gdb单步调试逻辑错误
- 使用gdb调试段错误

r全速运行,where 定位段错误的位置

  • 使用流程:

(1)编译程序:-g选项:允许使用gdb进行调试

gcc main.c -g

(2)使用gdb调试程序

gdb ./可执行程序     即 gdb ./a.out

(3)gdb指令

l                    列出源代码

b  行号         在指定行打断点

r                    运行程序

p  变量名      查看变量的值

n                    执行下一行代码

q                   退出gdb    quit

s                    跟进一个函数

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐