memset 的实现以在 C 中设置整个字而不是逐字节设置
Implementation of memset to set a whole word instead of byte by byte in C
所以我正在尝试实现我的个人 MemSet
,它将与 memset
一样,但也:
尽可能复制单词大小的块,而不是逐字节复制。
保证dest对齐[=29=]
测试所有对齐可能性
所以这是我的代码:
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);
}
我在这里做什么?
在 dest
上创建一个 runner
到 运行 的无符号字符指针,但不更改它的地址,这样我就可以 return它作为一个 return 值。
创建一个准备就绪的 swap_word
数组,大小为 sizeof(size_t)
因为这个大小决定了我的机器是 32 位还是 64 位(因此 WORD
大小是 4 或 8 个字节。当我需要设置一个字时,这个数组将被交换。
运行 一个简单的 while loop
将检查是否还有超过 sizeof(size_t)
个字节要设置,如果没有,这意味着我们肯定赢了'能够设置一个完整的字,然后逐字节设置。
另一个逐字节设置字节的选项是,如果地址不是除以 4 或 8(同样,取决于机器),这意味着我将无法设置在不交叉 WORD Boundary
的情况下添加一个字,所以只需逐字节设置它们,直到我到达对齐地址。
唯一设置整字的选项只有在数据已经对齐到机器的WORD size
的情况下,然后才设置8个字节(只需使用我们之前做的swap_word
的数组,再往前加8个地址。
我将通过使用
的转换来做到这一点
*((void **)运行ner) = *((void **)swap_word);
这是我的测试文件:
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
.
所以我正在尝试实现我的个人 MemSet
,它将与 memset
一样,但也:
尽可能复制单词大小的块,而不是逐字节复制。
保证dest对齐[=29=]
测试所有对齐可能性
所以这是我的代码:
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);
}
我在这里做什么?
在
dest
上创建一个runner
到 运行 的无符号字符指针,但不更改它的地址,这样我就可以 return它作为一个 return 值。创建一个准备就绪的
swap_word
数组,大小为sizeof(size_t)
因为这个大小决定了我的机器是 32 位还是 64 位(因此WORD
大小是 4 或 8 个字节。当我需要设置一个字时,这个数组将被交换。运行 一个简单的
while loop
将检查是否还有超过sizeof(size_t)
个字节要设置,如果没有,这意味着我们肯定赢了'能够设置一个完整的字,然后逐字节设置。另一个逐字节设置字节的选项是,如果地址不是除以 4 或 8(同样,取决于机器),这意味着我将无法设置在不交叉
WORD Boundary
的情况下添加一个字,所以只需逐字节设置它们,直到我到达对齐地址。唯一设置整字的选项只有在数据已经对齐到机器的
的转换来做到这一点WORD size
的情况下,然后才设置8个字节(只需使用我们之前做的swap_word
的数组,再往前加8个地址。 我将通过使用*((void **)运行ner) = *((void **)swap_word);
这是我的测试文件:
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
.