函数
在函数定义处,对形参的改变不影响实参,形参是实参的临时拷贝
返回值类型 函数名(形参列表){ 语句代码块 }
函数←面向过程
良好的习惯:在语句前缩进使用4个空格,不要用Tab键
良好的习惯:在if或for代码块结束的 } 后面加注释,注明结束
良好的习惯:如果一行表达式中的运算符较多,多使用括号来区分各运算符的优先级,达到不了解运算优先级也能一目了然的效果
良好的习惯:不要写太过复杂的表达式,尽量拆分
良好的习惯:在传参时,如果传的是指针,而指针在函数中又不会发生修改,只是单纯的输入,在形参列表指针前面+const
函数都是会被调用的,main函数也不例外,main函数是被mainCRTStartup函数调用,再往上就是操作系统范畴,因为程序执行都是要被加载到内存的,所以后续需要操作系统来管理
函数调用就会形成栈帧
栈区是向下增长的,即向低地址扩大
eip保存的是要执行的下一条指令的地址
临时拷贝的形成是在函数调用之前,实参的临时拷贝是要压入栈的,栈顶指针esp,向上(低地址)扩大。变量的入栈顺序是形参列表从右向左
在函数调用之前,先将形参实例化,形参的临时拷贝要入栈,找到实参变量的地址,将实参变量的内容存在寄存器eax,ecx,然后将eax,ecx压入栈顶,esp上移。然后保存函数调用后的返回地址,即函数调用call汇编语句的下一句汇编语句的地址,压入栈,esp上移。开始执行函数,eip保存的地址变为函数起始地址,然后在函数内部依次向下执行,执行结束后返回之前保存的地址。
内存管理
exit :程序退出的函数 在头文件stdlib中
perror :说明错误原因。显示与上一个系统调用相关联的错误描述。它通常被用于处理和显示来自errno变量的错误信息。使用例子:perror( 字符串 )
使用perror时,需要提供一个字符串参数s,该参数所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。举例:fopen函数调用失败,并设置errno为错误码。然后,可以使用perror函数来打印与该错误码相关的描述性错误消息。
因为栈区空间是非常有限的,栈区空间内的栈帧出了作用域就销毁了,如果在栈区开辟的空间太大,也就是创建的栈帧太多,就会导致栈溢出(stack overflow)。所以需要在堆区开辟空间才能满足大空间的开辟,在堆区上开辟的空间,在使用结束时,需要及时free(p),p是这块空间的最低地址。堆区才能对用多少空间开多少空间而不浪费实现一个精准的管理,栈区不行
对开辟的空间进行初始化,如下图
一直向系统申请内存而从不释放,就会发生内存泄漏
程序退出之后,内存泄漏问题就会自动解决,像服务器后端程序这些常驻程序(加载进内存中永不退出)才会因为内存泄漏造成巨大影响。
free的本质是将堆空间和指针指向解除对应关系
内存管理的本质是空间时候申请,申请多少,空间什么时候释放,释放多少
可变参数列表
可变参数列表必须有一个确定参数,因为在栈帧结构中,实参的临时拷贝是连续存放的,知道一个临时变量的地址,就可以访问到所有的形参
va_list 是一个char*类型的指针,通过这个地址能找到其他的临时变量。让这个指针指向最后一个元素的确定地址
va_start 通过指针操作,按照类型,从可变部分,依次取参数。强转成char*,然后根据类型大小,考虑4字节对齐,向下依次读取参数
va_arg 把当前元素提取出来,arg指向下一个待访问元素
va_end 将指针置为空,避免野指针
实际传入的参数如果是char short float 编译器在编译的时候,会自动进行提升
所以printf中,必须有%d等,进行格式控制,%确定类型,有几个%就有几个参数,以此来确定参数个数和类型
原理:
1.可变参数列表的函数,也是函数调用,也要形成栈帧
2.栈帧形成前,临时变量从右向左入栈,各临时变量在内存空间是连续存放的
3.短整型在可变参数部分,会进行整型提升,因此在函数内部提取该数据的值时,要考虑提升之后的值
命令行参数
argument参数
main函数有3个参数:
1.int argc 命令行参数个数
2 . char* argv[] 指针数组,里面的指针分别指向每一个命令行字符串,这些字符串彼此用空格隔开,组成命令行参数
3.char* env[] 也是一个指针数组,里面的指针指向很多个字符串(环境变量)
命令行参数的作用,可以在main函数中,让不同的选项代表不同的意义。分别对带有各种选项的情况进行判断,如果符号就执行相对应的代码,表现出不同的功能
递归
递归本质上也是函数调用,形成和释放栈帧,时间+空间。由于资源有限,所以递归不能无限制递归下去。核心思想:大事化小+递归出口 目标问题的子问题也可以使用相同的方法解决
特点:效率一般,代码简单
迭代:效率较高,代码复杂
扩容
malloc和realloc
realloc分为原地扩容和异地扩容,原地扩容就是扩容的地址在原来的地址,异地扩容就是扩容的地址和原来的地址不一样 ,就是在堆区的另外一块地方重新找的一个更大的地方,然后将数据拷贝过去
pow
幂次方pow(10,2) 10的平方
柔性数组
表面上数组大小是0,实际是能编译通过的。只能在结构体的最后一个字段使用
本质上就是结构体的空间的后面的第一个元素的地址,在之后的malloc时,选择大小,就可以在结构体中形成变长数组
结构体的地址和结构体的第一个元素的地址在数值上是一样的