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结构体,主要代码如下

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

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

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

三、memset函数

1.memset函数的使用

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

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

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

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

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

ar:需要赋值的数组

value:需要赋值的值

n:需要赋值的字节个数

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

#include<stdio.h>
#include<string.h>

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;
}

2.编写memset函数 

#include<stdio.h>
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;
}

四、memcpy函数

1.memcpy函数的使用

memcpy函数头文件#include <string.h>。

声明如下:

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

str1、str2数组

n需要复制的字节长度

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

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

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

#include<stdio.h>
#include<string.h>

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]);
	}
}

五、函数

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

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

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

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

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

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

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

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

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

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

④执行memset函数体

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

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

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

#include <stdio.h>
#include <string.h>
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;
}

头文件不参与编译过程。

 

2.可见性

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

①块作用域

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

例如:下面的代码中

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

②全局作用域

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

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

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

③作用域解析符::

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

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

int max = 0;

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

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

int max = 0;

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

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。

全局变量未初始化不会占据内存,初始化才占据内存。

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

六、寄存器

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位。

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寄存器。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/147175.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot整合minio步骤

springboot整合minio步骤 springboot整合minio十分的简单&#xff0c;接下来使用springboot整合一下minio。 一、导入依赖 首先需要导入minio的依赖。 <!--maven引入minio排除okhttp依赖并添加高版本的okhttp依赖--><dependency><groupId>io.minio</g…

Git新建分支

修改代码之Git策略思考&#xff1a; 有三种办法&#xff1a; 需要在主分支上新建一个分支&#xff0c;不合并新建版本。其实也是先新建一个分支&#xff0c;然后合并到主分支&#xff0c;再删除分支。直接新建远程仓库。 考虑&#xff0c;3&#xff09;最浪费&#xff0c;其…

聚势启新,KaiwuDB 生态联盟沙龙首站落地长春

11月9日&#xff0c;由 KaiwuDB 联合和润集团、致远互联主办的“KaiwuDB 生态联盟沙龙”首站活动在吉林长春顺利举办。沙龙以“聚势&#xff0c;启新”为主题&#xff0c;邀请基础软硬件、应用软件、信息安全等产业链上下游伙伴企业到场&#xff0c;共同就产业数智化趋势下的新…

Nginx 是如何解决惊群效应的?

什么是惊群效应&#xff1f; 第一次听到的这个名词的时候觉得很是有趣&#xff0c;不知道是个什么意思&#xff0c;总觉得又是奇怪的中文翻译导致的。 复杂的说&#xff08;来源于网络&#xff09;TLDR; 惊群效应&#xff08;thundering herd&#xff09;是指多进程&#xff…

红黑树的概念和简单实现

目录 红黑树的概念红黑树的结构红黑树的插入红黑树的验证 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑…

二进制原码、反码、补码、移码

机器数&#xff1a;一个数在计算机中的二进制表示形式&#xff0c;称为这个数的机器数。符号位&#xff1a;机器数是带符号的&#xff0c;在计算机中用最高位作为符号位&#xff0c;0为正数&#xff0c;1为负数。真值&#xff1a;机器数由于含有符号位&#xff0c;所以机器数的…

搭建项目环境,集成ts和jest

前言 开新坑。 斥巨资购入大崔哥的 mini-vue 课程&#xff0c;为了改变自己东一榔头西一棒槌的学习状态&#xff0c;也是因为深刻思考了自己身无长物浑浑噩噩这么多年只会敲代码&#xff0c;别无出路&#xff0c;也只能提升自己继续走技术这条路&#xff0c;那提高技术绕不过…

6.HTML中表格标签

6.表格标签 表格是实际开发中非常常用的标签 6.1 表格的主要作用 表格主要用于显示、展示数据&#xff0c;因为它可以让数据显示的非常规整&#xff0c;可读性非常好。特别是后台展示数据的时候&#xff0c;能够熟练运用表格就显得十分重要。一个清爽简约的表格能够把繁杂的数据…

维基百科是非营利性机构 词条内容具有中立性、准确性、可靠性

维基百科对一些企业很有神秘性&#xff0c;自行操作很多次也没有成功建立维基百科&#xff0c;这一定是没有按照维基百科的规则和流程去操作。小马识途营销顾问提醒企业&#xff0c;维基百科是一种基于协作的在线百科全书&#xff0c;由维基媒体基金会运营。维基百科的创建流程…

React Virtual DOM及Diff算法

JSX到底是什么 使用React就一定会写JSX&#xff0c;JSX到底是什么呢&#xff1f;它是一种JavaScript语法的扩展&#xff0c;React使用它来描述用户界面长成什么样子&#xff0c;虽然它看起来非常像HTML&#xff0c;但他确实是javaScript&#xff0c;在React代码执行之前&#…

上海亚商投顾:沪指震荡反弹 鸿蒙、算力概念股集体爆发

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日窄幅震荡&#xff0c;创业板指冲高回落&#xff0c;市场热点继续轮动。华为鸿蒙概念股继续活跃&#…

“具有分布式能源资源的多个智能家庭的能源管理的联邦强化学习”文章学习一

一、摘要 本文提出了一种新型的联邦强化学习&#xff08;FRL&#xff09;方法&#xff0c;用于管理带有家电、太阳能光伏系统和储能系统的多个智能家庭的能源。 所提出的FRL方法的创新点在于开发了一种由本地家庭能源管理系统(LHEMS)和全局服务器(GS)组成的分布式深度强化学习(…

.net core中前端vue HTML5 History 刷新页面404问题

放到启动的应用程序的最后面 app.Run(async (context) > {context.Response.ContentType "text/html";await context.Response.SendFileAsync(Path.Combine(env.WebRootPath, "index.html")); });https://blog.csdn.net/lee576/article/details/88355…

实现线程的多种方式锁的介绍ThreadLocal线程池 详细总结(下)

本文主要介绍线程池的基本使用 上述其他介绍在上一篇文章中&#xff1a;实现线程的多种方式&锁的介绍&ThreadLocal&线程池 详细总结&#xff08;上&#xff09;-CSDN博客 线程池 5.1、为什么使用线程池 线程池可以看做是管理了 N 个线程的池子&#xff0c;和连…

Java的XWPFTemplate word生成列表

Java的XWPFTemplate工具类导出word.docx的使用_xwpftemplate 语法_youmdt的博客-CSDN博客 如果是表格的列表参考上面这篇文章即可&#xff0c;比较复杂的列表遍历暂时还没找到方法&#xff0c;只能手动创建表格了 上面是模板&#xff0c;非常简单&#xff0c;以为我们是要自己创…

高效免费办公神器——ONLYOFFICE入手指南

前言&#xff1a; 作为开发者&#xff0c;有时候经常为寻找适合的开发工具而苦恼&#xff1b;或者因为高昂的费用而犹豫不决&#xff1b;亦或喜欢的办公产品只能在单一的平台上使用&#xff0c;与其把时间花在复杂的工具使用上&#xff0c;不如节省出时间投入思考和技术的提升。…

云课五分钟-01课程在哪里-无需安装网页直达

此部分课程均为2015-2019年规划和设计&#xff0c;2020-2022年新版课程还在内测中。 现在想想当年还是很莽的&#xff0c;总想着一个网页云服务&#xff0c;把机器人相关不涉及硬件的课程全囊括。 无需安装个性定制即开即用随时随地云端复现…… 视频 云课五分钟-01课程在哪…

C++设计实现日志系统

转载&#xff1a;C设计实现日志系统 - 知乎 (zhihu.com) 日志系统几乎是每一个实际的软件项目从开发、测试到交付&#xff0c;再到后期的维护过程中极为重要的 查看软件代码运行流程、 还原错误现场、 记录运行错误位置及上下文等的重要依据。一个高性能的日志系统&#xff0c…

【ArcGIS Pro微课1000例】0032:创建具有指定高程Z值的矢量数据

本文讲解ArcGIS Pro中创建具有指定高程值的矢量数据的两种方法。 文章目录 一、独立创建1. 新建地图场景2. 新建shapefile3. 绘制多边形4. 添加高程字段5. 三维显示二、基于高程源创建1. 创建栅格范围2. 添加Z值字段3. 添加Z信息4. 要素更新Z值一、独立创建 1. 新建地图场景 …

Pytorch多GPU并行训练: DistributedDataParallel

1 模型并行化训练 1.1 为什么要并行训练 在训练大型数据集或者很大的模型时一块GPU很难放下&#xff0c;例如最初的AlexNet就是在两块GPU上计算的。并行计算一般采取两个策略&#xff1a;一个是模型并行&#xff0c;一个是数据并行。左图中是将模型的不同部分放在不同GPU上进…