Day18.C提高2

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值