C语言指针及数组的运行原理

C语言指针及数组的运行原理

文章目录

  • C语言指针及数组的运行原理
    • 一. 指针(汇编角度)
    • 二. 数组(汇编角度)
      • 2.1 数组的定义
      • 2.2 指针与数组结合
    • 三. 指令解释参考
      • 3.1 nop
      • 3.2 leave
      • 3.3 ret

这里涉及汇编,虚拟机这边采用的是64位,Intel架构,汇编语法是AT&T。

一. 指针(汇编角度)

编写一个指针调用程序的C语言文件pointer.c

这里我声明了一个函数test

*代表运用指针,int来指定指针所操作内存单元大小

主函数这边,我声明了一个变量num,并赋值。调用test函数,通过&符号来提取num地址值并传参。

#include 

void test(int *p){
	*p = 3;

};
int main(){
	int num = 1;
	test(&num);
	return 1;
}

编译C文件,生成汇编

// -S 只进行编译
// -fno-asynchronous-unwind-tables 过滤调试代码
[root@localhost practice]# gcc -S -fno-asynchronous-unwind-tables pointer.c

查看生成的汇编文件pointer.s

	.file	"pointer.c"
	.text
	.globl	test
	.type	test, @function
test:
	pushq	%rbp				// 开辟栈空间,栈基址bp指向该空间地址
	movq	%rsp, %rbp			// 栈顶针sp指向栈基址bp所指向的空间地址
	movq	%rdi, -8(%rbp)		// rdi寄存器的值放入rbp-8字节大小的空间里
	movq	-8(%rbp), %rax		// 将rbp-8的地址值,放入rax寄存器中
	movl	$3, (%rax)			// 把3赋予这个rax所指向的地址
	nop							// 无操作
	popq	%rbp				// 还原bp
	ret							// 弹出返回地址空间,放入ip指令地址寄存器
main:
	pushq	%rbp
	movq	%rsp, %rbp
	subq	$16, %rsp			// 将sp的地址空间-16byte  
	movl	$1, -4(%rbp)		// 将1赋值到 bp-4 的空间里
	leaq	-4(%rbp), %rax		// 将该空间地址取出放入rax寄存器
	movq	%rax, %rdi			// 将rax寄存器的地址放入rdi寄存去
	call	test				// 调用test函数
	movl	$1, %eax			// 将立即数返回
	leave
	ret

总结:可通过汇编文件看出,指针通过栈空间的地址,从而找到变量单元。并且指针必须严格声明类型,因为类型进而能告诉机器他所需内存大小是多少。换句话说指针的操控=栈内存地址的操控


二. 数组(汇编角度)

2.1 数组的定义

编写一个数组定义的C文件demo.c

#include 

int main(){
	int arr[] = {11,22,33};
	return 1;
}

编译C文件生成汇编

// -S 只进行编译
// -fno-asynchronous-unwind-tables 过滤调试代码
[root@localhost practice]# gcc -S -fno-asynchronous-unwind-tables pointer.c

查看生成的汇编文件demo.s

	.file	"demo.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$11, -12(%rbp)  // 将11存储到rbp-12的空间里
	movl	$22, -8(%rbp)	// 将22存储到rbp-8的空间里
	movl	$33, -4(%rbp)	// 将33存储到rbp-4的空间里
	movl	$1, %eax
	popq	%rbp
	ret

总结:不难通过上述汇编文件demo.s看出,定义的值分别放入栈空间里,并且按照低地址到高地址,依次放入。

2.2 指针与数组结合


编写一个指针与数组结合的C文件demo2.c

这里我编写了一个长度为3的数组,放入的是int类型

让指针取最后一个索引数组值

#include 

int main(){
	int arr[] = {11,22,33};
	int *p = &arr[2];
	return 1;
}

编译并生成汇编代码(上面步骤提及,不在重复)

	.file	"demo3.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$11, -20(%rbp)
	movl	$22, -16(%rbp)
	movl	$33, -12(%rbp)
	
	leaq	-20(%rbp), %rax  // 将bp-20的地址(可理解为arr[0]的地址)放入rax寄存器
	addq	$8, %rax		// 将rax的地址值加8byte大小(可理解为加完后变成arr[2]),并重新让如rax寄存器中
	movq	%rax, -8(%rbp)	// 将rax地址保存到 rbp-8的位置上
	movl	$1, %eax
	popq	%rbp
	ret

总结:通过将数组里各个索引下标,转换成地址值,供指针操作。

⭐️额外的一些指针与数组的操作

1.通过指针改变数组里的值

#include 

int main(){
	int arr[] = {11,22,33};
	int *p = &arr[2];
	*p = 44;
	return 1;
}
------
    .file	"demo4.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	$11, -20(%rbp)
	movl	$22, -16(%rbp)
	movl	$33, -12(%rbp)
        
	leaq	-20(%rbp), %rax
	addq	$8, %rax
	movq	%rax, -8(%rbp)
	movq	-8(%rbp), %rax
	movl	$44, (%rax)     // 改变数组指定下标为2的数值
	movl	$1, %eax
	popq	%rbp
	ret

2.通过指针的内存加减获取数组的值

#include 

int main(){
	int arr[] = {11,22,33};
	int *p = &arr[0]; //获取首个索引的4字节地址,
	int a = *(p+1);  // 获取第二个4字节地址
	return 1;
}
------------
    .file	"demo5.c"
	.text
	.globl	main
	.type	main, @function
main:
	pushq	%rbp
	movq	%rsp, %rbp
	
	movl	$11, -24(%rbp)
	movl	$22, -20(%rbp)
	movl	$33, -16(%rbp)
	
	leaq	-24(%rbp), %rax  //取地址
	
	movq	%rax, -8(%rbp)
	movq	-8(%rbp), %rax
	movl	4(%rax), %eax //rax地址值是-24(%rbp) ,加4字节后,变成了数组中第二个值
	movl	%eax, -12(%rbp)
	movl	$1, %eax
	popq	%rbp
	ret

三. 指令解释参考

3.1 nop

运行该指令时什么都不做,但是会占用一个指令的时间。

当指令间需要有延时(给外部设备足够的响应时间;或是软件的延时等),可以插入“NOP”指令。

Intel手册 Volume2 Chapter4.3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yJRSL1P-1681886307021)(C语言指针及数组的运行原理.assets/屏幕截图 2023-04-19 140238.png)]

3.2 leave

释放分配的栈空间,并还原bp,sp到原始指向的地址空间

Intel手册 Volume1 Chapter6.6.2

C语言指针及数组的运行原理_第1张图片

3.3 ret

删除由调用程序推送到堆栈上的任何参数和返回地址

Intel手册 Volume2 Chapter4.3
C语言指针及数组的运行原理_第2张图片

你可能感兴趣的:(计算机学习,c语言,汇编,intel)