C语言入门——第15节课

目录

一、优化编写的strcmp函数

 二、结构体对齐

三、memset函数

1.memset函数的使用

2.编写memset函数 

四、memcpy函数

1.memcpy函数的使用

 2.编写memcpy函数

五、函数

1.函数的编译链接和内存布局

2.可见性

①块作用域

②全局作用域

③作用域解析符::

3.生存期 

①auto

②register

③static

④extern

4.BSS 

六、寄存器

1.eax、ebx、ecx、edx

2.ax、bx、cx、dx

3.ah、bh、ch、dh

4.al 、bl  、cl 、dl  

5.ebp、esp、esi、edi 

①ebp

②esp

③esi

④edi

七、汇编语言知识

1.基本指令

①mov(move)

②jmp(Jump)

③ret(return)

④call

⑤lea(load effective address)

⑥push

⑦pop

2.寻址方案

①直接寻址方案

②间接寻址方案

③基变址寻址方案


一、优化编写的strcmp函数

首次编写在第14课,下面是老师对代码做的优化。

第一次优化,因为输出的结果是正数或者负数,或者0。具体输出可以看上一节课。

我们直接使用两个ASCII码值相减就可以得到正数、负数或者0啦,不需要使用随机数来生成。

int strcmp(const char* ar, const char* br)
{
    if (ar == NULL || br == NULL) return 0;
	while (*ar == *br && *ar != '\0' && *br != '0')
	{
		ar++;
		br++;
	}
	return (int)(*ar - *br);
}
int main()
{
	char ar[10] = { 0 };
	char br[10] = { 0 };
	scanf("%s %s", &ar, &br);
	int result = strcmp(ar, br);
	printf("%d", result);
}

第二次优化,更加简洁,更加重要的是这种思维方式,我们使用*ar和*br进行比较,输出也是对应的数值,何不直接作为判断条件呢!

int strcmp(const char* ar, const char* br)
{
	if (ar == NULL || br == NULL) return 0;
	int k = 0;
	while ((k = (*ar - *br)) == 0 && *ar++ && *br++);
	return k;
}
int main()
{
	char ar[10] = { 0 };
	char br[10] = { 0 };
	scanf("%s %s", &ar, &br);
	int result = strcmp(ar, br);
	printf("%d", result);
}

 二、结构体对齐

设置一个srtuct结构体,主要代码如下

我们需要知道它在内存中占用多少空间?

C语言入门——第15节课_第1张图片

C语言入门——第15节课_第2张图片

首先开辟10个空间存放10char类型的字符s_id,接下来开辟20个空间存放char类s_name,此时需要接着存放int类型的数,先要判断此时空间是不是int类型的整数,(10+20)/4不能整除所以,需要给s_age空间前面添加两个空间,使空间数是4的整数倍。

详细的结果体内存问题可以看C语言入门——第五课-CSDN博客

三、memset函数

C语言入门——第15节课_第3张图片

1.memset函数的使用

详情:C++ -- memset()函数 - 手磨咖啡 - 博客园 (cnblogs.com)

memset()函数的用法详解-CSDN博客

memset函数在头文件中,它可以对一整片连续的内容逐字节赋值。

memset对于较大的结构体或者数组,统一初始化效率较高。

void*memset(void* ar,int value,size_t n)

ar:需要赋值的数组

value:需要赋值的值

n:需要赋值的字节个数

注意:它对于char以外的数组赋值时,只能赋值为0或-1。

#include
#include

struct Data
{
	char num[10];
	int m;
};
int main()
{
	struct Data a;
	memset(&a, 0, sizeof(a));
	a.num[sizeof(a.num) - 1] = '\0';
	for (int i = 0; i < sizeof(a.num); i++)
	{
		printf("%d ", a.num[i]);
	}
	printf("%d", a.m);
	return 0;
}

C语言入门——第15节课_第4张图片

2.编写memset函数 

#include
void* memset(void* dest, int value, size_t n)
{
	if (dest == NULL || n < 1)return 0;
	char* br = (char*)dest;
	for (int i = 0; i < n; i++)
	{
		br[i] = (char)value;
	}
	return br;
}

int main()
{
	char num[10];
	int a[10] = { 0 };
	memset(num, 0, sizeof(num));
	memset(a, 0, sizeof(a));
	for (int i = 0; i < sizeof(num); i++)
	{
		printf("%d ", num[i]);
	}
	printf("\n");
	for (int j = 0; j < sizeof(num); j++)
	{
		printf("%d ", a[j]);
	}
	return 0;
}

C语言入门——第15节课_第5张图片

四、memcpy函数

1.memcpy函数的使用

memcpy函数头文件#include

声明如下:

void *memcpy(void * dest,void * src,size_t n)

str1、str2数组

n需要复制的字节长度

将str2的n个字节复制给str1。

返回值:返回一个str1的指针。

详情:C 库函数 – memcpy() | 菜鸟教程 (runoob.com)

#include
#include

int main()
{
	char dest[10] = {0};
	char src[10] = "abcdefg";
	memcpy(dest, src, 10);
	printf("%s", dest);
}

 2.编写memcpy函数

struct Student
{
	char s_id[20];
	char s_name[20];
	int age;
};
void* my_memcpy(void* dest, void* src, size_t n)
{
	assert(dest != NULL || src != NULL || n < 1);
	char* br = (char*)dest;
	char* ar = (char*)src;
	for (int i = 0; i < n; i++)
	{
		br[i] = ar[i];
	}
	return br;
}
int main()
{
	int ar[5] = { 1,2,3,4,5 };
	int br[5];
	char str[10];
	struct Student s1 = { "09001","lzy",23 }, s2;
	my_memcpy(&s2, &s1, sizeof(s1));
	my_memcpy(br, ar, sizeof(ar));
	//br[sizeof(br) - 1] = '\0';
	printf("%s\n", s2.s_id);
	printf("%s\n", s2.s_name);
	printf("%d\n", s2.age);
	int n = sizeof(br) / sizeof(br[0]);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", br[i]);
	}
}

C语言入门——第15节课_第6张图片

五、函数

1.函数的编译链接和内存布局

C语言入门——第15节课_第7张图片

代码区:被执行的二进制机器代码

数据区:全局变量、静态局部变量、静态全局变量、字符常量

堆区:动态请求内存大小,malloc请求内存,free释放内存

栈区:函数调用的时候用到栈区,存储函数的参数、局部变量还有函数之间的关系。

详细解释一下函数之间的关系是什么意思:

例如在下面代码中,主函数处调用了memset函数,函数需要中止主函数,

C语言入门——第15节课_第8张图片

①给memset函数建立栈帧空间(栈帧是有限的,在VS中栈帧空间为1M,Linux的栈大小在8-10M之间。)

②保护现场:主函数的返回地址和运行状态入栈。

③将实参和形参进行结合,给形参分配内存空间、给局部变量分配内存空间。

④执行memset函数体

⑤函数体执行结束后,将返回值存储在寄存器中,释放局部变量所占用的栈空间。

⑥调用主函数的运行状态和返回地址。

⑦将返回值给主函数并继续执行主函数。

#include 
#include 
void* memset(void* dest, int value, size_t n)
{
	if (dest == NULL || n < 1)return 0;
	char* br = (char*)dest;
	for (int i = 0; i < n; i++)
	{
		br[i] = (char)value;
	}
	return br;
}

int main()
{
	char num[10];
	memset(num, 0, sizeof(num));
	for (int i = 0; i < sizeof(num); i++)
	{
		printf("%d ", num[i]);
	}
	printf("\n");
	return 0;
}

头文件不参与编译过程。

C语言入门——第15节课_第9张图片

 

2.可见性

可见性是编译链接过程的要求,生存期是执行才谈论到的概念。

①块作用域

可见性在对应花括号里可见,一个花括号可以看作是一个块,所以我们也可以说变量的可见性在块中可见。函数也可以看作一个块,此时的作用域为函数作用域。

例如:下面的代码中

int main()函数的花括号内,a,b在整个花括号可见,在if花括号里还有一个b,这个b在if的花括号内可见,此时int main()函数的变量b被隐藏。因为在函数域内如果有变量重名的情况,服从局部优先原则。具体示例如下:

C语言入门——第15节课_第10张图片

②全局作用域

全局变量的可见性为向下可见。

在下面的例子中,我们可以明确的看出作用域向下可见,只有在上面定义过的变量,在定义之后才能进行其他操作。如:g_max。

g_min显示错误,是因为在下面才定义它,它向上不可见,所以会报错:未定义的变量。

C语言入门——第15节课_第11张图片

③作用域解析符::

 如果局部变量和全局变量重名,全局变量的值被隐藏,如果想要使用全局变量的值可以使用作用域解析符将隐藏的全局变量显示出来。

隐藏全局变量的值,如下:

int max = 0;

int main()
{
	int max = 10;
	printf("%d", max);
}

C语言入门——第15节课_第12张图片

使用作用域解析符::,示例如下

int max = 0;

int main()
{
	int max = 10;
	printf("%d", ::max);
}

C语言入门——第15节课_第13张图片

3.生存期 

变量的存储类型决定了变量的生存期。变量的存储类型说明符分为:auto、register、static、extern。

①auto

自动适配类型需要初始化,空间始于块始,释放于块终,由系统自动控制。在C11中有了新的标准,以后说。

示例代码如下:

int main()
{
	int a = 10;
	auto b = &a;
	auto p = "lizeyu";
	auto c = 'a';
}

②register

为了提高程序效率,读取数据速度更快,会使用register,将变量保存在寄存器。为了避免空间长时间被占用,我们只对个别的局部变量使用register。这个说明符不推荐使用。

③static

详情看:C语言入门——第六课-CSDN博客

 

④extern

详情看:C语言入门——第六课-CSDN博客

4.BSS 

BSS段是全局变量未初始化存储的位置,全局变量未初始化被存放在bss段,全部会被初始化为0。

C语言入门——第15节课_第14张图片全局变量未初始化不会占据内存,初始化才占据内存。

全局变量被存放在数据区,函数被存放在代码区。

六、寄存器

1.eax、ebx、ecx、edx

eax:累加器,用来存放运算结果

ebx:基址寄存器,存放指向数据段的指针

ecx:计数器,用于循环计数

edx:数据寄存器,用于存放运算溢出的数据

2.ax、bx、cx、dx

ax:eax的低十六位,用来存放运算结果。

bx:ebx的低十六位基址寄存器,存放指向数据段的指针。

cx:16位计数器,是ecx的低16位。

dx:16位数据寄存器,是edx的低16位。

3.ah、bh、ch、dh

eax、ebx、ecx、edx的高8位。

4.al 、bl  、cl 、dl  

eax、ebx、ecx、edx的低8位。

C语言入门——第15节课_第15张图片

5.ebp、esp、esi、edi 

①ebp

ebp是扩展基址指针,通常被用作帧指针。在函数调用中,ebp 用于指向当前函数的栈帧的底部,这有助于在函数中访问局部变量和参数。通过保存和恢复 ebp 的值,可以有效地实现对局部变量和参数的访问。 

②esp

esp是扩展栈指针,指向当前栈的顶部。随着函数的调用和返回而动态变化。

③esi

源索引寄存器,用于存储源操作数的地址,便于上下文操作,例如:字符串的复制,它是一个通用寄存器。

④edi

目标索引寄存器,用于存储目标操作数的地址,便于上下文操作,例如:字符串的复制,它是一个通用寄存器。

七、汇编语言知识

1.基本指令

这些是x86汇编语言中常见的指令,用于进行各种操作。以下是它们的简要解释

①mov(move)

mov 指令用于将数据从一个位置复制到另一个位置。

示例:mov eax, ebx 表示将 ebx 寄存器的值复制到 eax 寄存器。

②jmp(Jump)

无条件跳转到指定位置。

示例:jmp label 将程序控制转移到标记(label)处。

③ret(return)

从函数返回到调用函数的位置。

④call

用于调用函数,将程序控制转移到指定的函数入口点。

示例:call function 将程序控制转移到名为 function 的函数的入口点。

⑤lea(load effective address)

将一个关于地址计算的结果存储到寄存器中,不是加载实际数据。

示例:lea eax, [ebx + 4]ebx + 4 的地址计算结果存储到 eax 寄存器中。

⑥push

将数据推到栈上。

例:push eaxeax 寄存器的值推送到栈上。

⑦pop

将栈上的数据压出到寄存器上。

示例:pop ebx 将从栈顶弹出的数据存储到 ebx 寄存器中。

2.寻址方案

①直接寻址方案

mov eax,1234 将1234存储到eax寄存器中

②间接寻址方案

mov [ebx],0X0012,将0x0012给ebx寄存器地址下的空间。这里 ebx 寄存器中存储的是一个内存地址,而不是一个具体的数值。

③基变址寻址方案

基址变址寻址是一种结合基址和变址的方式。基址是一个寄存器,存储了数据块的起始地址,而变址是一个寄存器,存储了偏移量。这种方式常用于数组和结构体的访问,基址不变,变址会变。

例如:MOV AX, [BX+SI] ; 将(BX+SI)地址处的值加载到AX寄存器。 

你可能感兴趣的:(C语言,c语言,开发语言)