Cracking C++(12): 实现 memset

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)

NAME
     memset – fill a byte string with a byte value

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <string.h>

     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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值