# C标准库实现-glibc为例

glibc 和 libc 都是 Linux 下的 C 函数库。

  • libc 是 Linux 下的 ANSI C 函数库
  • glibc 是 Linux 下的 GNU C 函数库

# 目录

[toc]

# 实现memcpy「腾讯」

  • 这是一个C语言实现的内存拷贝函数,它的作用是从源地址src拷贝num个字节到目的地址dest

为什么在构造的时候把它强制转换成char*,而不是int*等其他类型的呢?因为,memcpy可以拷贝任意类型的内存,所以如果转成int*,那么它每次只能拷贝4个字节,而对于字符串或是结构体的拷贝,它们的大小可能不是4的整数倍,这样就会导致拷贝的失败,所以为了可以拷贝全部内存,要一个一个字节的拷贝,而char*恰好满足这样的条件.

void *memcpy (void * dest, const void * src, size_t num) {
    assert(NULL !=src && NULL !=dest); //「坑1」断言,防止空指针
		char *m_dest=(char *)dest;
    const char *m_src=(const char *)src;
  	for(size_t i=0; i<num; ++i){
      m_dest[i]=m_src[i];
    }
  	return dest;
}
1
2
3
4
5
6
7
8
9

# 实现memmove「腾讯」

  • memmove函数的作用是从源内存地址src处开始拷贝n个字节到目标内存地址dest处。它的优点是可以处理源内存和目标内存有重叠的情况,memcpy函数则不能处理这种情况

最后,memcpy在内存没有重复的情况下能够正确复制,若有重叠情况则复制结果错误,但是它的效率比memmove高。所以,在确定没有重复的情况下用memcpy,在不确定是否有内存重复的情况用memmove。

如上!三种情况描述如下:

  • dst<=src
    • 此时,正向的拷贝即可,不会覆盖数据
  • src<dst<src+n
    • 此时,需要逆向的拷贝!否则会覆盖数据
  • src+n<=dst
    • 此时,正向的拷贝即可,不会覆盖数据
void * memmove ( void * dst,const void * src,size_t count)
{
   assert(NULL !=src && NULL !=dst);//「坑1」断言,防止空指针
   void * ret = dst;
   if (dst <= src || (char *)dst >= ((char *)src + count))
   {  // 若dst和src区域没有重叠,则从起始处开始逐一拷贝
      while (count--)
      {
         *(char *)dst = *(char *)src;
         dst = (char *)dst + 1;
         src = (char *)src + 1;
      }
   }
   else
   {  // 若dst和src 区域交叉,则从尾部开始向起始位置拷贝,这样可以避免数据冲突
      dst = (char *)dst + count - 1;
      src = (char *)src + count - 1;
      while (count--)
      {
         *(char *)dst = *(char *)src;
         dst = (char *)dst - 1;
         src = (char *)src - 1;
      }
   }
   return(ret);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void * memmove ( void * dst,const void * src,size_t n)
{
   assert(NULL !=src && NULL !=dst);//「坑1」断言,防止空指针
   char *m_dst=(char *)dst;
   char *m_src=(char *)src;
  
   if( m_dst<= m_src || m_dst>=(m_src+n) ){
        memcpy(m_dst,m_src,n); //情况1和3的正向拷贝
   }
   else{
        while(n--){
            m_dst[n]=m_src[n]; //逆向拷贝
        }
   }
   return dst;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 实现strcpy

把从src地址开始且含有'\0'结束符的字符串复制到以dst开始的地址空间,返回值的类型为char*

char *strcpy(char *dst, char *src){
  assert( NULL!=dst && NULL!=src );
  char *ret=dst;
  while( '\0'!=(*src) ){
    *(dst++)=*(src++);
  }
  *dst='\0';
  return dst;
}
1
2
3
4
5
6
7
8
9

# 实现strlen

计算给定字符串的⻓度

int strlen( const char *str ){
  assert( NULL!=str );
  int len=0;
  while( '\0'!=(*str) ){
    ++len;
    ++str;
  }
  return len;
}
1
2
3
4
5
6
7
8
9

# 实现strcat

作⽤是把src所指字符串添加到dest结尾处

char * strcat( char *dst, const char *src ){
  assert( NULL!=dst && NULL!=src ); //assert( dst && src); 也是可以的
  char *ret=dst;
  while( '\0'!=(*dst) ){
    ++dst;
  }
  
  //从'\0'添加开始
  while( '\0'!=src ){
    (*dst)=(*src);
    ++dst;
    ++src;
  }
  *dst='\0';
  
  return ret;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 实现strcmp「学习」

⽐较两个字符串设这两个字符串为str1,str2

若str1 == str2,则返回0

若str1 < str2,则返回负数

若str1 > str2,则返回正数

int strcmp(const char *s1, const char *s2){
  	assert( NULL!=s1 && NULL!=s2 ); //assert( s1 && s2); 也是可以的
    int i;
    for (i = 0; s1[i] != '\0' && s2[i] != '\0'; ++i){
        if (s1[i] != s2[i]){
          return (s1[i] - s2[i]); 
        }
    }
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11