文章目录
- 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 <string.h>
, 能否自行实现 memset()
函数呢?
2. memset: API 说明
2.1 查询 man 文档
man memset
MEMSET(3) Library Functions Manual MEMSET(3)NAMEmemset – fill a byte string with a byte valueLIBRARYStandard C Library (libc, -lc)SYNOPSIS#include <string.h>void *memset(void *b, int c, size_t len);DESCRIPTIONThe memset() function writes len bytes of value c (converted to an unsignedchar) to the string b.RETURN VALUESThe 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