Linux C语言之指针与内存管理

1. 指针

1.1 交换两个数

int *p = &a; 将指针 p 指向 a;

#include <stdio.h>

void change(int *a, int *b){	// 使用指针交换数值
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    int a = 5;
    int b = 3;
    change(&a, &b);
    printf("a = %d\nb = %d\n", a, b);
    return 0;
}

2. gdb 工具的使用

2.1 安装 gdb 调试工具工具:

$ sudo apt-get install gdb

编译生成支持调试的 .out 文件

$ gcc -g main.c -o main.out  # 在编译的时候,产生调试信息

2.2 gdb 的使用

使用命令:gdb main.outgdb ./main.out 进行调试

$ gdb ./main2.out		# 调试main2.out
(gdb) list		# 查看源码
(gdb) l			# 查看源码

(gdb) start		# 开始调试	会列出当前所在行号

(gdb) p a		# print the value of variable a
(gdb) n			# next 单步执行(执行上一条命令,显示下一条命令)
(gdb) s			# 到某个函数处使用 s 进入这个函数

(gdb) bt		# backtrace 查看函数堆栈
#0  change (a=5, b=3) at main2.c:5
#1  0x000000000040116d in main (argv=1, argc=0x7fffffffcc48) at main2.c:13

(gdb) f 1		# 切换函数栈 1
(gdb) frame 0	# 切换到栈 0
(gdb) info stack	# 查看堆栈的使用情况

(gdb) q			# 退出调试

当使用指针进行调试时,进入函数后会出现地址:

此时传入的是地址,两个地址 相差4个字节

(gdb) p a		# 打印地址 a
(gdb) p *a		# 打印 a 的内容

3. 内存

Q:32bits 的系统最大内存为4G?

A:32bits 的系统地址总线为32位,可使用的内存为 2 32 2^{32} 232 bits,最大内存为4G

Q:内存是如何分配的?

A:由操作系统统一来管理内存,操作系统调用函数

Q:为什么64位的系统中 gdb 打印的地址都是48位的?

A:受硬件处理器的限制,目前X86_64处理器的地址总线只有48位,一般默认64位地址的高16位为0,因此地址的实际范围为:0x00 00 00 00 00 000x 7f ff ff ff ff ff

OS 功能

  • 对内存编号

  • 对内存进行规划(如64位操作系统里面,将地址的低48位分配给用户使用,剩下的作为操作系统内核)


4. 操作系统对内存的管理

对下面代码进行调试:

#include <stdio.h>

int global = 0;

int rect(int a, int b){
	static int count = 0;
	count++;
	global++;
	int s = a * b;
	return s;
}

int quadrate(int a){
	static int count = 0;
	count++;
	global++;
	int s = rect(a, a);
	return s;
}

int main()
{
	int a = 5;
	int b = 3;
	int* pa = &a;
	int* pb = &b;
	int *pglobal = &global;
	int (*pquadrate)(int a) = &quadrate;
	int s = quadrate(a);
	return 0;
}

4.1 查看变量的地址:

(gdb) start
(gdb) p &a
(int *) 0x7fffffffcb28		# 4 bits;a 的首地址为 0x7fffffffcb28 (字节作单位)
(gdb) p &b
(int *) 0x7fffffffcb24		# 4 bits
(gdb) p &s
(int *) 0x7fffffffcb2c		# 编译后进行了优化 (地址顺序和代码不一定一致)
(gdb) p &pquadrate
(int (**)(int)) 0x7fffffffcb30

(gdb) p &pa
(int **) 0x7fffffffcb48		# 64 bits
(gdb) p &pb
(int **) 0x7fffffffcb40		# 64 bits
(gdb) p &global
(int **) 0x7fffffffcb38		

(gdb) p &rect
(int (*)(int, int)) 0x401102 <rect> # 先加载 (0x401139-0x401102=rect编译后的总长度)
(gdb) p &quadrate
(int (*)(int)) 0x401139 <quadrate> 	# 后加载

(gdb) p &global
(int *) 0x40402c <global>	# 0x400402c 比代码段地址大

4.2 函数的调用:

// 当 main() 函数执行到:int s = quadrate(a);
    输入 s 进入函数 quadrate():
       // 当 quadrate() 函数执行到: int s = rect(a, a);
           输入 s 进入函数 rect():
               此时打印栈的使用情况:

得到下面的结果:

函数调用时,栈从上往下增长,main 函数在最外层,地址更高,内层的函数地址更低,最内层的函数最先返回 FILO

4.3 内存的分配

(gdb) bt
#0  rect (a=5, b=5) at storage.c:7
#1  0x0000000000401171 in quadrate (a=5) at storage.c:17
#2  0x00000000004011b9 in main () at storage.c:29

(gdb) f 0
(gdb) p &count
$8 = (int *) 0x404030 <count>		# rect 中的静态变量 更小

(gdb) f 1
(gdb) p &count
$9 = (int *) 0x404034 <count>		# quadrate 中的静态变量(quadrate 调用 rect)

内存的示意图如下图所示:

内存存放内容特点
代码段代码编译后生成的机器二进制码存放处(指令)地址向上增长
数据段全局变量、常量、静态变量向上增长
存放变量、函数调用地址接近 0x7fffffffffffffff,地址向下增长
内核操作系统相关的内容,用户不得修改地址高于 0x7fffffffffffffff

在 gdb 中查看内存:

x/3d 0x7fffffffd114	# 从给定地址开始显示3个数,用 decimal 显示

5. 指针和数组

5.1 指针的运算

int array[5];

int* p = &array[0];
p += 3;			// p 前进3格 指向 array[3]

p = array;
p[2] = 5;		// *(p+2) = 5 即 array[2] = 5

5.2 字符数组

#include <stdio.h>

int main()
{
	char str1[] = "hello";			// 栈内存分配时分配在
	char *str2 = "world";			// "world" 存放在代码段,指针str2指向 "world"
	char str3[10];
	printf("input the value: ");
	scanf("%s", str3);
	printf("str1 is %s\n", str1);
	printf("str2 is %s\n", str2);
	printf("str3 is %s\n", str3);	// str3是数组,也是地址
	return 0;
}

调试上述代码:

$ gcc -g test.c -o test.out
$ gdb ./test.out

(gdb) p str1		# 数值
$1 = "hello"
(gdb) p str2		# 指针	存储在代码段
$2 = 0x402004 "world"

尝试 scanf("%s", str2) 回报错,原因是 str2 位于代码段,不能修改。

char str1[] = "hello";之后加上一句 str1[3] = '\0'后再打印str1,只得到了 “hel”


参考内容:

1:Linux 指针与内存

2:Linux C语言指针与内存

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值