gpt4 book ai didi

assembly - 我的 memset 实现结果只打印更改,而不是整个结果字符串

转载 作者:行者123 更新时间:2023-12-04 10:28:58 24 4
gpt4 key购买 nike

这是来自 memset movq giving segfault 的相同实现实验
我一直在打印 memset 的结果,它似乎只打印出更改,而不是字符串的其余部分。

experimentMemset:   #memset(void *ptr, int value, size_t num)

movq %rdi, %rax #sets rax to the first pointer, to return later

.loop:
cmp $0, %edx #see if num has reached limit
je .end

movq %rsi, (%rdi) #copies value into rdi
inc %rdi #increments pointer to traverse string
subl $1, %edx #decrements count
jmp .loop
.end:
ret



int main {

char str[] = "almost every programmer should know memset!";
printf("MEMSET\n");
my_memset(str, '-', 6);
printf("%s\n", str);

}

我的输出:------

cplusplus.com 的正确输出:------ 每个程序员都应该知道 memset!

最佳答案

movq将高零存储在 int value 中,而不仅仅是低字节。这将终止 C 字符串。 并且写入超过 ptr+length 的末尾您的调用者通过!

使用 mov %sil, (%rdi)存储 1 个字节。

(实际上,您使用 movq 存储了 8 个字节,包括根据调用约定允许包含垃圾的高 4 个字节,因为它们不是 32 位 int value 的一部分。有了这个调用者,它们”不过,也将为零。)

您可以通过使用调试器或更好的测试工具检查内存内容来检测到这一点 .下次这样做。一个更好的调试调用者会使用 writefwrite打印完整的缓冲区,您可以将其通过管道输入 hexdump -C .或者只是使用 GDB 的 x命令转储内存字节。

你只查%edxsize_t num的低4字节在 %rdx .如果您的来电者要求您准确设置 4GiB 的内存,您将返回而不存储任何内容。

您可以通过将条件分支放在底部来使循环更加紧凑。您可以将声明更改为 unsigned num ,或者你可以修复你的代码。

.globl experimentMemset
experimentMemset: #memset(void *ptr, int value, size_t num)

movq %rdi, %rax #sets rax to the first pointer, to return later

test %rdx, %rdx # special case: size = 0, loop runs zero times
jz .Lend
.Lloop: # do{
mov %sil, (%rdi) # store the low byte of int value
inc %rdi # ++ptr
dec %rdx
jnz .Lloop # }while(--count);
.Lend:
ret

它甚至不再有任何指令:我只是将 cmp/jcc 拉出循环以使其成为跳过循环检查,然后打开 jmp在底部进入 jcc读取 dec 设置的标志.

效率

当然,一次存储 1 个字节的效率非常低,即使我们优化了循环,以便更多的 CPU 可以在每个时钟 1 次迭代中运行它。对于高速缓存中的中型阵列,现代 CPU 可以使用 AVX 或 AVX512 存储快 32 到 64 倍。并且可以接近与 rep stosb 对齐的缓冲区的速度字符串指令,在具有 ERMSB 功能的 CPU 上。是的,x86 有一条指令可以实现 memset !

(或者对于更宽的模式, wmemsetrep stosd 。在没有 ERMSB 但有快速字符串的 CPU 上(PPro 和 IvyBridge 之前的更高版本), rep stosd 或 stosq 更快,因此您可以 imul $0x01010101, %esi, %eax 广播低字节。)
# slowish for small or misaligned buffers
# but probably still better than a byte loop for buffers larger than maybe 16 bytes
.globl memset_ermsb
memset_ermsb: #memset(void *ptr, int value, size_t num)

mov %rdx, %rcx # count = num
mov %esi, %eax # AL = char to set
rep stosb # destination = RDI
ret

真正的 memset 实现使用 SIMD 循环,因为这对于小缓冲区或未对齐的缓冲区更快。关于优化 memset/memcpy 的文章很多。 Glibc 的实现非常聪明,是一个很好的例子。

内核代码不能轻易使用 FPU/SIMD 所以 rep stos memset 和 rep movsb memcpy 确实在 Linux 内核的现实生活中得到了使用。

关于assembly - 我的 memset 实现结果只打印更改,而不是整个结果字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60502624/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com