1.strlen()

1)计算给定字符串的长度,不包括’\0’在内

unsigned int strlen(const char *s)
{
assert(NULL != s);//如果条件不满足,则终止程序
unsigned int length = ;
while (*s++ != '\0')
++length;
return length;
}

2.strcmp()

1)比较两个字符串,若str1、str2字符串相等,则返回零;若str1大于str2,则返回正数;否则,则返回负数

int strcmp(const char *str1, const char *str2)
{
assert(NULL != str1 && NULL != str2);
while(*str1 && *str2 && *str1 == *str2)
{
++str1;
++str2;
}
return *str1 - *str2;
//若相等,则*str1 - *str2 = '\0' - '\0' = 0;否则,*str1 - *str2 != 0;因为前面的位都相等,所以只需要比较当前位来确定返回值
}

3.strcat()

1)将src所指向的字符串添加到dest结尾处(会覆盖dest结尾处的'\0'

2)src和dest所指内存区域不可以重叠且dest必须有足够的空间(sizeof(dest)-strlen(dest)必须>=strlen(src)+1)来容纳src的字符串

char* strcat(char* dest, const char* src)
{
assert(NULL != dest && NULL != src);//如果条件不满足,则终止程序
char* temp = dest;
while ('\0' != *temp)//while循环出来temp指向原字符串的NULL空字符的位置
++temp;
while (*src != '\0')
*temp++ = *src++;
return dest;
}

4.strcpy()

1)把字符串src(包括'\0')复制到dest

2)src和dest所指内存区域不可以重叠且dest必须有足够的空间(sizeof(dest)>=strlen(src)+1)来容纳src的字符串

3)返回值char*而不是void,实现了链式表达(就是让调用strcpy的时候可以方便一些,在调用的时候可以一连串(链式)写下来)

char* strcpy(char* dest, const char *src)
{
assert(NULL != dest && NULL != src);
char * temp = dest;
while (*src != '\0')
*temp++ = *src++;
return dest;
}

5.strncpy()

1)将以字符串src所指向的地址开始的前n个字节复制到dest中,并返回dest

2)如果src的前n个字符不含NULL结束符,则dest不会以NULL字符结束

3)如果n>strlen(src)+1,则以'\0'填充dest,直到复制完n个字节

4)src和dest所指内存区域不可以重叠且dest必须有足够的空间(sizeof(dest)>=strlen(src)+1)来容纳src的字符串

5)dest和n应该满足:sizeof(dest)>=n

char* strncpy(char* dest, const char* src, unsigned int n)
{
assert(NULL != dest && NULL != src);
char* temp = dest;
while (n > && *src != '\0')
{
*temp++ = *src++;
--n;
}
if (n > )
{
*temp = '\0';
}
return dest;
}

6.memset()

1)将以s所指向的地址开始的前n个字节用ch替换,并返回s

2)常用于对较大的数组进行清零操作

void* memset(void* s, int ch, unsigned int n)
{
assert(NULL != s);
char* temp = s; //VS2017中这里会报错,gcc不会
while (n > )
{
*temp++ = (char)ch; //将int转化为char,截去高24位,只保留低8位
--n;
}
return s;
}

3)注意:不能把memset当作万能的初始化工具,应该只把memset当作清0的工具,因为memset是以字节(8bits)为单位用第二个参数ch进行赋值的

int main()
{
int dest[];//这里是int
memset(dest, , sizeof(dest));
for (int i = ; i < sizeof(dest) / sizeof(int); ++i)
printf("%d\n", dest[i]); return ;
}
程序输出: int main()
{
int dest[];//这里是int
memset(dest, , sizeof(dest));
for (int i = ; i < sizeof(dest) / sizeof(int); ++i)
printf("%d\n", dest[i]); return ;
}
程序输出:

程序解读:

  1)1的二进制 : 00000000000000000000000000000001(32位),在memset源码中1将强制类型转换成char(1字节占8位):00000001(8位)

  2)memset函数传入的是sizeof(dest),也就是8(字节),所以n=8,进行8次while循环

  3)temp的类型是char*,所以会8位8位地去赋值

  4)所以while循环结束,从起始地址开始的值为:0000000100000001000000010000000100000001000000010000000100000001(64位)

  5)memset()调用结束后,dest的每个元素按照int类型读取(按32位读取),所以dest[0]=00000001000000010000000100000001(32位)=16843009,dest[1]=00000001000000010000000100000001(32位)=16843009

7.memcpy()

1)将以src所指向的地址开始的前n个字节的任意内容(不仅限于字符串)到拷贝到dest

2)src和dest所指内存区域不可以重叠,否则拷贝出来的数据将是错误的

void* memcpy(void* dest, const void* src, unsigned int n)
{
assert(NULL != dest && NULL != src);
void* temp = dest;
for (int i = ; i < n; ++i)
*((char*)temp + i) = *((char*)src + i); //1个字节1个字节地拷贝,未知类型,不能++
return dest;
}

8.memmove()

1)memmove也是将以src所指向的地址开始的前n个字节的任意内容(不仅限于字符串)到拷贝到dest,但是可以它可以处理src和dest所指内存区域存在重叠的情况(src<dest<src+n)

2)memmove这个函数名称中有"move"这个单词,但实际上并不是"移动"了。这个函数名称有它的历史原因:因为有了memcpy函数后,发现这个函数在使用时容易出现问题,于是又发明了一个能够处理重叠的memcpy函数,但为了保证兼容性依然保留了memcpy函数

void* memmove(void* dest, const void* src, unsigned int n)
{
assert(NULL != dest && NULL != src);
void* temp = dest;
if (dest > src && dest < src + n)//内存重叠
{
for (int i = n - ; i >= ; --i)//从高地址向低地址拷贝
*((char*)temp + i) = *((char*)src + i);
}
else//内存不重叠
{
for (int i = ; i < n; ++i)
*((char*)temp + i) = *((char*)src + i);
}
return dest;
}

9.mmap()

  转载自:https://blog.csdn.net/qq_36675830/article/details/79283113

9.1mmap()

1)mmap是一种内存映射的方法,即将一个磁盘文件映射到进程的虚拟地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应

2)内存映射的好处:实现这样的映射关系后,进程一旦读写操作这一段内存,系统会自动回写到对应的磁盘文件上,这样一来,对文件的操作就不必再调用read、write等系统调用函数,效率会很高

3)mmap()的函数原型

void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
/*
其中:
addr:映射区起始地址,通常设为NULL,由系统指定。
length:将文件的多大长度映射到内存
prot:映射区的保护方式,可以是:
PROT_EXEC:映射区可被执行
PROT_READ:映射区可被读取
PROT_WRITE:映射区可被写入
PROT_NONE:映射区不能存取
flag:映射区的特性,可以是:
MAP_SHARD:对映射区的写入数据会复制回文件,且允许其他映射该文件的进程共享
MAP_PRIVATE:对映射区域的写入数据会产生一个映射的复制(copy - on - write),对此区域所做的修改不会写回到原文件
其他标识这里就不再写出来了,可以通过man mmap查看
fd:由open返回的文件描述符,表示要映射的文件
offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0, 表示从文件头开始映射
*/

4)vm_area_struct结构:linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问

5)在用户层创建虚拟地址空间中的映射区域

  • mmap()在内存映射段寻找一段空闲的满足要求的连续的区域,为此区域分配一个vm_area_struct结构,接着对这个结构进行初始化
  • 将新建的vm_area_struct结构插入进程的虚拟地址区域链表或树中

6)从用户层进入内核层,调用内核函数mmap()(不同于用户空间库函数)完成文件物理地址和进程虚拟地址的一一映射关系

  • 内核函数mmap(),其原型为:
int mmap(struct file* filp, struct vm_area_struct* vma);

7)注意:mmap只是创建了建立文件磁盘地址和虚拟内存区域的映射关系,没有任何文件拷贝操作

9.2常规文件的读写和内存映射段读写的区别

1)进程对常规文件的读写过程:

  • 进程调用read()函数发起读文件的请求
  • 内核通过查找进程文件符表,从而找到此文件的inode
  • 通过inode检查次文件页是否已经缓存在物理页中,如果已缓存,则直接返回物理页上的内容
  • 如果未缓存,即发生缺页,于是进行页面置换,将数据从磁盘复制到物理页上缓存
  • 由于页缓存处在内核空间不能被用户进程直接寻址,所以还需要将页缓存中数据再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取

2)进程对建立了内存映射的文件的读写过程:

  • 进程的读或写操作访问虚拟地址空间的映射地址,通过查询页表,发现这虚拟页并未被缓存,引发缺页异常。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到物理内存中,即没有被缓存,因此引发缺页异常
  • 然后内核利用已经建立好的映射关系,将磁盘文件直接拷贝至位于用户空间的内存映射段上,于是进程就可以直接读取文件内容了
  • 如果进程的写操作改变了其内容,一定延迟后系统会自动回写对应磁盘地址,也即完成了写入到文件的过程,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了

3)区别:常规文件操作需要从磁盘到内核空间的页缓存再到用户空间的两次数据拷贝;而读写进行了内存映射的文件,只需要从磁盘到用户空间的一次数据拷贝,因此内存映射段效率很高

c语言中几个常见的库函数strlen、strcmp、strcat、strcpy、strncpy、memset、memcpy、memmove、mmap的更多相关文章

  1. 理解C语言中几个常见修饰符

    写在前面 今天下午一个同事问「register」关键字是什么作用?噢,你说的是「register」啊,它的作用是……脑袋突然断片儿,我擦,啥意思来着,这么熟悉的陌生感.做C语言开发时间也不短了,不过好 ...

  2. C语言中最常用的标准库函数

    标准头文件包括: <asset.h>      <ctype.h>       <errno.h>       <float.h> <limits ...

  3. 【C++】异常简述(一):C语言中的异常处理机制

    人的一生会遇到很多大起大落,尤其是程序员. 程序员写好的程序,论其消亡形式无非三种:无疾而终.自杀.他杀. 当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要 ...

  4. C语言中库函数strstr的实现

    在C语言中库函数strstr()函数表示在一个字符串str1中查找另一个字符串str2,如果查到则返回str2在str1中首次出现的位置,如果找不到则返回null. char* strstr(char ...

  5. C语言中的栈和堆

    原文出处<http://blog.csdn.net/xiayufeng520/article/details/45956305#t0> 栈内存由编译器分配和释放,堆内存由程序分配和释放. ...

  6. C语言中的堆与栈20160604

    首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!一.前言介绍:C语言程序经过编译连接后形成编译.连接后形成的二进制映像文件是静态区域由代码段和数据段(由二部分部分组成:只读数据 段,未初始化数 ...

  7. 在C语言中利用PCRE实现正则表达式

    1. PCRE简介 2. 正则表达式定义 3. PCRE正则表达式的定义 4. PCRE的函数简介 5. 使用PCRE在C语言中实现正则表达式的解析 6. PCRE函数在C语言中的使用小例子 1. P ...

  8. Java C# C语言中的占位符

    一般拼接一段字符串在编程中是很常见的事,下面简单做个总结: 什么是占位符?占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号. 1.Java中处理方法: package com.amos; ...

  9. C语言中内存分配那些事儿

    C程序的内存结构 C语言的之所以复杂,首先它的内存模型功不可没.不像某些那样的高级语言只需要在使用对象的时候,用new创建.所有之后的事情,你不需要操心.对于C语言,所有与内存相关的东西,都需要熟悉, ...

随机推荐

  1. Nginx_status显示结果详解

    打开:http://aabb.com/nginx_status会有如下显示Active   connections: 2872server   accepts  handled requests294 ...

  2. Struts和Hibernate使用总结

    1   struts.xml重定向时报错    action cannot be found in the namespace/     http://blog.csdn.net/greetturin ...

  3. 英语广播原声听力100篇MP3及听力原文

    =============7.6================ Passage 031- 人工智能对人类的利与弊From a personal assistant, to doing searche ...

  4. virtual abstract override new 几点学习

    1.Vitual方法和普通方法区别为:继承其的子类可以用override/new在重载此方法,也可以不重载其方法,有方法体(可以写语句),override修饰则调用子类方法2.abstract类中抽象 ...

  5. 在windows 2008 R2中SQl Server 2008中代理启动失败的一个原因总结

    启动SQL代理的时候报错如下: 关调用实时(JIT)调试而不是此对话框的详细信息,请参见此消息的结尾. ************** 异常文本 **************System.NullRef ...

  6. 树莓派Zero W GPIO控制

    作者:陈拓 chentuo@ms.xab.ac.cn 2018.06.09/2018.07.05 0.  概述 本文介绍树莓派 Zero W的GPIO控制,并用LED看效果. 0.1 树莓派GPIO编 ...

  7. Installation failed with message INSTALL_CANCELED_BY_USER.

    Installation failed with message INSTALL_CANCELED_BY_USER. It is possible that this issue is resolve ...

  8. distinct top執行順序

    select distinct top 3 from table; 先distinct后top

  9. 安装64位的oracle数据库, 使用自带的sqldeveloper

    个人感觉这个东西比plsql要好用, 虽然界面有点丑, 整个使用与plsql也没多大区别, 这里是他的位置C:\oracle_11g\product\11.2.0\dbhome_1\sqldevelo ...

  10. 51. N-Queens (Array; Back-Track, Bit)

    The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens ...