目录
一、优化编写的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.寻址方案
①直接寻址方案
②间接寻址方案
③基变址寻址方案
首次编写在第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博客
详情: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;
}
#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;
}
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);
}
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]);
}
}
代码区:被执行的二进制机器代码
数据区:全局变量、静态局部变量、静态全局变量、字符常量
堆区:动态请求内存大小,malloc请求内存,free释放内存
栈区:函数调用的时候用到栈区,存储函数的参数、局部变量还有函数之间的关系。
详细解释一下函数之间的关系是什么意思:
例如在下面代码中,主函数处调用了memset函数,函数需要中止主函数,
①给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;
}
头文件不参与编译过程。
可见性是编译链接过程的要求,生存期是执行才谈论到的概念。
可见性在对应花括号里可见,一个花括号可以看作是一个块,所以我们也可以说变量的可见性在块中可见。函数也可以看作一个块,此时的作用域为函数作用域。
例如:下面的代码中
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);
}
变量的存储类型决定了变量的生存期。变量的存储类型说明符分为:auto、register、static、extern。
自动适配类型需要初始化,空间始于块始,释放于块终,由系统自动控制。在C11中有了新的标准,以后说。
示例代码如下:
int main()
{
int a = 10;
auto b = &a;
auto p = "lizeyu";
auto c = 'a';
}
为了提高程序效率,读取数据速度更快,会使用register,将变量保存在寄存器。为了避免空间长时间被占用,我们只对个别的局部变量使用register。这个说明符不推荐使用。
详情看:C语言入门——第六课-CSDN博客
详情看:C语言入门——第六课-CSDN博客
BSS段是全局变量未初始化存储的位置,全局变量未初始化被存放在bss段,全部会被初始化为0。
全局变量被存放在数据区,函数被存放在代码区。
eax:累加器,用来存放运算结果
ebx:基址寄存器,存放指向数据段的指针
ecx:计数器,用于循环计数
edx:数据寄存器,用于存放运算溢出的数据
ax:eax的低十六位,用来存放运算结果。
bx:ebx的低十六位基址寄存器,存放指向数据段的指针。
cx:16位计数器,是ecx
的低16位。
dx:16位数据寄存器,是edx
的低16位。
eax、ebx、ecx、edx的高8位。
eax、ebx、ecx、edx的低8位。
ebp是扩展基址指针,通常被用作帧指针。在函数调用中,ebp
用于指向当前函数的栈帧的底部,这有助于在函数中访问局部变量和参数。通过保存和恢复 ebp
的值,可以有效地实现对局部变量和参数的访问。
esp是扩展栈指针,指向当前栈的顶部。随着函数的调用和返回而动态变化。
源索引寄存器,用于存储源操作数的地址,便于上下文操作,例如:字符串的复制,它是一个通用寄存器。
目标索引寄存器,用于存储目标操作数的地址,便于上下文操作,例如:字符串的复制,它是一个通用寄存器。
这些是x86汇编语言中常见的指令,用于进行各种操作。以下是它们的简要解释
mov
指令用于将数据从一个位置复制到另一个位置。
示例:mov eax, ebx
表示将 ebx
寄存器的值复制到 eax
寄存器。
无条件跳转到指定位置。
示例:jmp label
将程序控制转移到标记(label)处。
从函数返回到调用函数的位置。
用于调用函数,将程序控制转移到指定的函数入口点。
示例:call function
将程序控制转移到名为 function
的函数的入口点。
将一个关于地址计算的结果存储到寄存器中,不是加载实际数据。
示例:lea eax, [ebx + 4]
将 ebx + 4
的地址计算结果存储到 eax
寄存器中。
将数据推到栈上。
例:push eax
将 eax
寄存器的值推送到栈上。
将栈上的数据压出到寄存器上。
示例:pop ebx
将从栈顶弹出的数据存储到 ebx
寄存器中。
mov eax,1234 将1234存储到eax寄存器中
mov [ebx],0X0012,将0x0012给ebx寄存器地址下的空间。这里 ebx
寄存器中存储的是一个内存地址,而不是一个具体的数值。
基址变址寻址是一种结合基址和变址的方式。基址是一个寄存器,存储了数据块的起始地址,而变址是一个寄存器,存储了偏移量。这种方式常用于数组和结构体的访问,基址不变,变址会变。
例如:MOV AX, [BX+SI] ; 将(BX+SI)地址处的值加载到AX寄存器。