Day18.C提高2
一、函数调用模型(需要自己理解的知识点)
1.1 函数调用流程
在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将压入栈中的数据弹出(出栈,pop),但是栈容器必须遵循一条规则:先入栈的数据最后出栈(先进后出).
在经典的操作系统中,栈总是向下增长的。压栈的操作使得栈顶的地址减小,弹出操作使得栈顶地址增大。
栈在程序运行中具有极其重要的地位。最重要的,栈保存一个函数调用所需要维护的信息,这通常被称为堆栈帧(Stack Frame)或者活动记录(Activate Record).一个函数调用过程所需要的信息一般包括以下几个方面:
函数的返回地址;
函数的参数;
临时变量;
保存的上下文:包括在函数调用前后需要保持不变的寄存器。
宏函数不是函数,宏函数在一定场景下效率更高,对于频繁使用的,并且代码量短小的函数 因为宏函数没有与普通函数调用的开销(函数压栈,跳转,返回等),因此使用宏函数。
1.2 调用惯例
一个调用惯例一般包含以下几个方面:
函数参数的传递顺序和方式
1.函数的传递有很多种方式,最常见的是通过栈传递。函数的调用方先将将参数压入栈中,函数自己再从栈中将参数取出。
对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序:从左向右,还是从右向左。有些调用惯例还允许使用寄存器传递参数,以提高性能。
在C语言中,对于子函数的调用中,一般情况下都是(假设都是在栈区存储的变量)
(1):先将子函数的形参压入栈中;
(2):然后将传入的实参的值放入到形参中
(3):然后跳转到返回地址,也就是在主函数中调用子函数的代码的下一行代码的地址;
(4):然后再执行子函数,将子函数中内部定义的局部变量压入栈中,局部变量的值如果
和子函数的形参有关,就把形参的值赋与局部变量,如果没有使用形参的值,就直接在内
存中存储定义局部变量时赋予的值。
(5):若需要返回值,且返回值较小的时候,则将运算的运算结果存储与寄存器中,将寄
存器的值赋予主函数中定义的用来存储返回值的变量,否则将内容存储在栈中,将栈中的内
容赋予主函数中接收的变量;
(6):函数调用过程结束,释放栈区空间
2.栈的维护方式
为了在链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰。不同的调用惯例有不同的名字修饰策略。
在c语言里,存在着多个调用惯例,而默认的是cdecl(由函数调用方释放内存,参数由右向左进行入栈).任何一个没有显示指定调用惯例的函数都是默认是cdecl惯例。比如我们上面对于func函数的声明,它的完整写法应该是:
int _cdecl func(int a,int b);
注意: _cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用__attribute__((cdecl)).
1.3 栈的生长方向和内存存放方向
1.栈的生长方向:自顶向下生长,由高地址(栈顶)向低地址(栈底)生长
2.单个数据在内存中的存储,比如0xaabbccdd,在内存中的存储顺序应该为ddccbbaa,
即高位字节存放的是数据的高位地址(小端模式)
二、指针
指针不管几级指针,不管是哪种类型的指针,默认占用4个字节。
1.野指针和空指针
(1):空指针
NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可
以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。
对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未执行任何东西,
因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NUL针。
(2):野指针
在使用指针时,要避免野指针的出现:
导致野指针的三种抢矿:
1.指针变量未初始化(牢记指针必须初始化)
指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内
存。
2.指针释放后未置空(使用指针后记得置NULL)
有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名
字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此
时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
3.指针操作超越变量作用域(不要返回局部变量的地址)
不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
2.指针的步长
指针的步长指的是,当指针+1时候,移动1个指针类型字节大小的距离。
指针的类型:不单单决定指针的步长,还决定解引用的时候从给定地址开始取类型大小的字节数
示例代码:
#define _STR_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stddef.h>
struct Person
{
int a;
char b;
char buf[64];
int d;
};
void test01()
{
struct Person p1 = { 10,'a',"hello python!",20 };
char b;
//offsetof是计算偏移量的宏函数,定义在stddef.h头文件中,即计算b距离Person类型的首地址的偏移量
printf("结构体中b的偏移量为:%d\n", offsetof(struct Person, b));
printf("d = %d\n", *(int*)((char*)&p1 + offsetof(struct Person, d)));
}
int main()
{
test01();
return 0;
}
3.指针作为函数参数
指针做函数参数,具备输入和输出特性:
输入:主调函数分配内存,被调函数使用内存
输出:被调函数分配内存,主调函数释放内存
代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void printString(const char* arr)
{
printf("%s\n",arr);
}
void printfArrString(const char** arr,int len)
{
for (int i = 0; i < len; i++)
{
printf("%s\n",arr[i]);
}
}
//1.主调函数分配内存,被调函数使用内存 指针的输入特性
void test01()
{
//堆上分配内存
char* s = malloc(sizeof(char) * 100);
memset(s, 0, 100);
strcpy(s, "hello world");
printString(s);
free(s);
//栈上分配内存
char* str[] = { "aaaaaa","bbbbbb","cccccc","dddddd","eeeeee", };
int strLen = sizeof(str) / sizeof(str[0]);
//printf("%d\n", strLen);
printfArrString(str, strLen);
}
//2.输出特性 被调函数分配内存,主调函数使用内存
void allocateSpace(char** temp)
{
char* p = malloc(100);
memset(p, 0, 100);
strcpy(p, "hello world");
*temp = p;
}
void test02()
{
char* p = NULL;
allocateSpace(&p);
printf("%s\n", p);
if (p != NULL)
{
free(p);
p = NULL;
}
}
int main()
{
//test01();
test02();
return 0;
}
三、字符串
字符串格式化
1.sprintf函数
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:
根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,
直到出现字符串结束符 '\0' 为止。
参数:
str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:
成功:实际格式化的字符个数
失败: - 1
2.sscanf函数
#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
功能:
从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:
成功:实际读取的字符个数
失败: - 1
sscanf代码示例:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void test01()
{
char ch1[] = "helloworld@qq.conm";
char buffer1[1024] = { 0 };
sscanf(ch1, "%[^@]", buffer1); //截取到helloworld
//sscanf(ch1, "%s@%s", buffer1,buffer2);
printf("%s\n", buffer1);
sscanf(ch1, "%*[^@]@%s", buffer1);//任意一个非@符号的字符都不截取,然后匹配到@后截取所有字符串
printf("%s\n", buffer1);
}
int main()
{
test01();
return 0;
}