文章目录
- main函数很普通
- main函数之前调用了什么
- main函数和自定义函数的对比
- 变量名只为人而存在
- goto是循环的本质
- 指针变量
- 指针是一个特殊的数字
- 汇编层面看指针
- 数组和指针
- 数组越界问题
- 低端地址越界
- 高端地址越界
- 引用就是指针
main函数很普通
main函数是第一个被调用的函数吗?在用户视角看来main函数的确程序的入口,但是在CPU视角下,main函数仅仅只是一个普通函数,和用户自定义的其他函数没有任何的区别。
main函数之前调用了什么
Linux环境:
_start->__libc_start_main->main
每一个Linux进程的入口函数都是_start,_start是一段直接由汇编语言编写的函数,它负责的工作就是把程序的命令行参数以及环境变量压入栈中,此时环境变量和参数一起存放在一个数组中
为了把环境变量单独提取出来,_start紧接着会调用__lib_start_main函数构建一张环境变量表,并进行一些全局变量的初始化工作,随后再进入main函数执行用户程序,再main函数退出时进行收尾操作例如全局变量的释放。这么一来,main函数似乎也只不过是一个被调用的函数,它只是默认被注册为用户代码的入口而已,也就是说,用户代码入口不一定非要是main函数
main函数和自定义函数的对比
一直以来我们编写C/C++程序时都是约定俗成地添加一个main函数来启动程序(因为不这么做往往会报错),这种情况一度让不少人认为main函数具有特殊的地位,能够得到CPU的青睐,其实不然,CPU眼中main函数啥也不是就是很普通的函数
int main(){return 0;
}
int func(){return 0;
}
通过汇编观察main和func的区别,会发现它们所对应的汇编指令居然完全一致
main:push rbpmov rbp, rspmov eax, 0pop rbpret
func:push rbpmov rbp, rspmov eax, 0pop rbpret
2个函数所做的操作都是一样的
- 建立函数栈帧 push rbp / mov rbp,rsp
- 将返回值拷入寄存器 mov eax,0
- 释放函数栈帧并返回 pop rbp / ret
gcc有一个命令可以改变用户代码的入口,使得用户指定其他函数作为程序起点
gcc -nostartfiles -efunc test.c
意思是编译test.c不使用系统的标准启动文件,将程序起点设置为func函数;一般不推荐这么做,因为使用标准启动文件代表着你需要自己为func瞻前顾后,这无疑是在自找麻烦
变量名只为人而存在
变量对程序员来说并不陌生,我们无时无刻都在使用变量帮助我们记忆,因为一个好的变量名可以大大提高源代码的可读性;尽管如此,对于可执行文件来说它并不需要存储所谓的变量名(release模式编译链接),CPU只需要知道一个逻辑地址就可进行读写操作,也就是说在发布模式编译链接时所有的变量名都会被转换称逻辑地址。
因此,我们可以给出关于变量的定义:=变量就是逻辑地址的一个别名,它向上以字符串形式以供人阅读记忆,向下被转成地址值供CPU访存
int a=0;
int main(){a=2;return 0;
}
所对应的汇编文件
main:push rbpmov rbp,rspmov DWORD PTR [rip+0x0],0x2 # e <main+0xe>//将立即数0x2写入到rip值偏移量为0的位置,DWORD PTR标识4字节mov eax,0x0pop rbpret
可以看出a=2这条代码所对应的汇编是mov DWORD PTR [rip+0x0],0x2,CPU只需要通过几个逻辑地址相对寻址就可以确定内存的哪个位置需要被赋值为2
goto是循环的本质
虽然说不鼓励在编写C/C++程序时随意的使用goto,但是不代表goto不值得探究,早期的循环其实都是通过goto语句来实现的,只不过随着程序越来越大,过多的goto语句打破了程序的结构性使得源码难以维护,进而衍生出了结构性更强的for、while、do语句,它们都是在底层实现上都继承的goto的机制
void test_for(){for(int i=0;i<10;++i){}
}
void test_while(){int i=0;