Cracking C++(12): 实现 memset

文章目录

    • 1. 目的
    • 2. memset: API 说明
      • 2.1 查询 man 文档
      • 2.2 为什么使用 `int` 类型传入填充值 `c`?
      • 2.3 为什么需要返回值?
    • 3. memset: naive 实现
      • 3.1 byte-wise 的实现
      • 3.2 加速实现: word-wise
      • 3.3 其他实现
    • 4. References

1. 目的

最近了解到 deepdream_c 这个开源项目, 作者用 C89 实现了 deepdream. 没错, C89 是30年前的技术了, 但作者仍用它完整了算法的实现, 可以说非常克制了。

受到作者风格的影响, 我在用 C99 实现 lenet, 涉及到相关 API 的使用我也尽可能的克制自己。不禁要问: 如果不使用 #include , 能否自行实现 memset() 函数呢?

2. memset: API 说明

2.1 查询 man 文档

man memset
MEMSET(3)                     Library Functions Manual                     MEMSET(3)

NAME
     memset – fill a byte string with a byte value

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include 

     void *
     memset(void *b, int c, size_t len);

DESCRIPTION
     The memset() function writes len bytes of value c (converted to an unsigned
     char) to the string b.

RETURN VALUES
     The memset() function returns its first argument.

2.2 为什么使用 int 类型传入填充值 c

查看 memset 文档,函数原型:

 void * memset(void *b, int c, size_t len);

而直觉认为应该是这样才对:

 void * memset(void *b, char c, size_t len);

参数 c 为什么用 int 类型而不是 int 类型呢?因为 memset() 函数在 C 语言很早期就存在了,那时候没有 function prototype, 而没有 function prototype 的情况下, 不能传入 char 类型。如果你强行传 char 类型, 就会自动提升为 int 类型。因此,那时候定义 memset 函数, 参数 c 用的就是 int 类型。 后来, C 语言逐渐完善, 需要先声明函数再使用, 这时候确实用 char c 更合适。(参考[1])

2.3 为什么需要返回值?

参考[3] 解释认为, memset() 可用于链式使用:

char a[200];
strcpy(memset(a, 0, 200), "bla");

In order to use the function as an argument for another function such as sprintf

也就是说, 直接把 memset(x, y, z) 作为另一个函数的参数。

3. memset: naive 实现

3.1 byte-wise 的实现

了解了 int c 参数的历史后, 我们知道参数 c 的实际有效值仅仅是最后1个byte, 因此写出正确实现:

void* memset(void* s, int c, size_t n)
{
    char* p = (char*)s;
    char x = c & 0xff; // most machines (PC, Android) are little-endian.
    for (size_t i = 0; i < n; i++)
    {
        p[i] = x;
    }
    return s;
}

3.2 加速实现: word-wise

每次写入4个字节(uint32_t类型), 不足4字节的部分,逐字符拷贝。参考了文献[4].

void memset(void* s, int c, size_t n)
{
    uint32_t* p = (char*)s;
    // most machines (PC, Android) are little-endian.
    uint32_t x = c & 0xff;
    uint32_t xx = x;
    xx |= (xx << 8);
    xx |= (xx << 16);

    int nn = n >> 2;
    int remain = n - (nn << 2);
    size_t i = 0;
    fsor (; i < nn; i++)
    {
        *p = x;
        p++;
    }

    char* q = (char*)p;
    for (; remain; remain--)
    {
        *q = x;
        q++;
    }

    return s;
}

说明: xx |= (xx << 8) 把 xx 左移8位,累加到 xx 上。再执行 xx |= (xx << 16) 则等于原始的 xx 作为一个 byte 拷贝了4份放入到了一个 uint32 长度的内存中, 相当于 xx xx xx xx.

3.3 其他实现

可以用 SIMD 加速,暂时没尝试。

4. References

  • [1] Why does memset take an int instead of a char?
  • [2] Why does memset take in an int instead of an unsigned char?
  • [3] What’s the use of memset() return value?
  • [4] https://github.com/Smattr/memset/blob/master/memset.c

你可能感兴趣的:(C/C++,c++,memset,内存,实现,C)