memset 的实现以在 C 中设置整个字而不是逐字节设置

Implementation of memset to set a whole word instead of byte by byte in C

所以我正在尝试实现我的个人 MemSet,它将与 memset 一样,但也:

所以这是我的代码:

void *MemSet(void *dest, int c, size_t n)
{
    unsigned char *runner = (unsigned char *)dest;
    
    size_t i = 0;
    
    unsigned char swap_word[sizeof(size_t)];
    
    for (i = 0; i < sizeof(size_t); ++i)
    {
        swap_word[i] = (unsigned char)c;
    }
    
    if (NULL == dest)
    {
        return (NULL);
    }
    
    while (n > 0)
    {
        /* setting byte by byte */
        if (n < sizeof(size_t) || (((size_t)runner & (sizeof(size_t) - 1)) != 0))
        {
            *runner++ = (unsigned char)c;
            --n;
            printf("Byte written\n"); /* for debugging */
        }
        else
        {
            /* setting a whole word */
            *((void **)runner) = *((void **)swap_word);
            runner += sizeof(size_t);
            n -= sizeof(size_t);
            printf("Word written\n"); /* for debugging */
        }
    }
    return (dest);
}

我在这里做什么?

这是我的测试文件:

int array[] = { 2, 3 };
    
int main () 
{
    for (i = 0; i < 2; i++)
    {
        printf("Before MemSet, target is \"%d\"\n\n", array[i]);
    }
    if (NULL == MemSet(array, 3, 2 * sizeof(int)))
    {
        fprintf(stderr,"MemSet failed!\n");
        
    }
    for (i = 0; i < 2; i++)
    {
        printf("After MemSet, target is \"%d\"\n\n", array[i]);
    }
    return (0);
}

输出为:

Before Memset, target is "2"

Before Memset, target is "3"

Word written
After Memset, target is "50529027"

After Memset, target is "50529027"

为什么元素不是“3”?两个都? 我在这里使用

MemSet(array, 3, 2 * sizeof(int))

这两个元素理论上都需要设置为3,因为数组在内存中使用了2*sizeof(int)个空间,我都设置为3 ].

你怎么看? 还有,我如何检查我的对齐是否有效?

谢谢。

你的函数有多个问题:

  • 您在每次迭代中测试字长移动,这可能比简单的字节操作慢。

  • *((void * *)runner) = *((void **)swap_word); 不正确,因为它违反了别名规则,并且因为 swap_word 可能无法正确对齐 void * 类型。

你应该运行 分开循环:

  • 第一个对齐目标指针
  • 第二个设置完整单词,一次可能不止一个
  • 最后一个设置尾随字节(如果有的话)

这是一个例子:

#include <limits.h>
#include <stdio.h>
#include <stdint.h>

// assuming uintptr_t has no padding bits
void *MemSet(void *dest, int c, size_t n) {
    if (dest != NULL) {
        unsigned char *p = dest;
        if (n >= sizeof(uintptr_t)) {
            // align destination pointer
            // this test is not fully defined but works on all classic targets
            while ((uintptr_t)p & (sizeof(uintptr_t) - 1)) {
                *p++ = (unsigned char)c;
                n--;
            }
            // compute word value (generalized chux formula)
            uintptr_t w = UINTPTR_MAX / UCHAR_MAX * (unsigned char)c;
            // added a redundant (void *) cast to prevent compiler warning
            uintptr_t *pw = (uintptr_t *)(void *)p;
            // set 16 or 32 bytes at a time
            while (n >= 4 * sizeof(uintptr_t)) {
                pw[0] = w;
                pw[1] = w;
                pw[2] = w;
                pw[3] = w;
                pw += 4;
                n -= 4 * sizeof(uintptr_t);
            }
            // set the remaining 0 to 3 words
            while (n >= sizeof(uintptr_t)) {
                *pw++ = w;
                n -= sizeof(uintptr_t);
            }
            p = (unsigned char *)pw;
        }
        // set the trailing bytes
        while (n --> 0) {
            *p++ = (unsigned char)c;
        }
    }
    return dest;
}

但是请注意,上面的代码不太可能击败 memset(),因为:

  • 如果已知目标指针是对齐的,或者如果 CPU 允许未对齐访问,编译器可能会扩展上述逻辑内联以获取常量大小,跳过对齐测试。
  • 库可能会使用 SIMD 或 REP/STOS 等专用指令来增加吞吐量,具体取决于实际目标 CPU。

结果令人惊讶的原因是 int 跨越 4 个字节,每个字节都设置为 3,因此整数的结果值为 0x03030303,这正是50529027.