> 文章列表 > C语言指针及数组的运行原理

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

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 <stdio.h>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				// 还原bpret							// 弹出返回地址空间,放入ip指令地址寄存器
main:pushq	%rbpmovq	%rsp, %rbpsubq	$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			// 将立即数返回leaveret

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


二. 数组(汇编角度)

2.1 数组的定义

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

#include <stdio.h>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	%rbpmovq	%rsp, %rbpmovl	$11, -12(%rbp)  // 将11存储到rbp-12的空间里movl	$22, -8(%rbp)	// 将22存储到rbp-8的空间里movl	$33, -4(%rbp)	// 将33存储到rbp-4的空间里movl	$1, %eaxpopq	%rbpret

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

2.2 指针与数组结合


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

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

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

#include <stdio.h>int main(){int arr[] = {11,22,33};int *p = &arr[2];return 1;
}

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

	.file	"demo3.c".text.globl	main.type	main, @function
main:pushq	%rbpmovq	%rsp, %rbpmovl	$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, %eaxpopq	%rbpret

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

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

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

#include <stdio.h>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	%rbpmovq	%rsp, %rbpmovl	$11, -20(%rbp)movl	$22, -16(%rbp)movl	$33, -12(%rbp)leaq	-20(%rbp), %raxaddq	$8, %raxmovq	%rax, -8(%rbp)movq	-8(%rbp), %raxmovl	$44, (%rax)     // 改变数组指定下标为2的数值movl	$1, %eaxpopq	%rbpret

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

#include <stdio.h>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	%rbpmovq	%rsp, %rbpmovl	$11, -24(%rbp)movl	$22, -20(%rbp)movl	$33, -16(%rbp)leaq	-24(%rbp), %rax  //取地址movq	%rax, -8(%rbp)movq	-8(%rbp), %raxmovl	4(%rax), %eax //rax地址值是-24(%rbp) ,加4字节后,变成了数组中第二个值movl	%eax, -12(%rbp)movl	$1, %eaxpopq	%rbpret

三. 指令解释参考

3.1 nop

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

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

Intel手册 Volume2 Chapter4.3

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

3.2 leave

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

Intel手册 Volume1 Chapter6.6.2

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

3.3 ret

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

Intel手册 Volume2 Chapter4.3
C语言指针及数组的运行原理