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

Posted

技术标签:

【中文标题】我实现 memset 的结果只打印更改,而不是整个结果字符串【英文标题】:Result of my implementation of memset only prints the changes, and not the entire result string 【发布时间】:2020-03-03 08:03:48 【问题描述】:

这是来自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 %rsi, (%rdi) 写入 8 个字节,而不是您想要的 1 个字节。 你可能想要movb %sil, (%rdi) 【参考方案1】:

movqint 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 倍。在具有 ERMSB 功能的 CPU 上,使用rep stosb 字符串指令可以接近对齐缓冲区的速度。是的,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 stosmemset 和 rep movsbmemcpy 确实在 Linux 内核中用于现实生活。

【讨论】:

以上是关于我实现 memset 的结果只打印更改,而不是整个结果字符串的主要内容,如果未能解决你的问题,请参考以下文章

为啥它只打印文本文件的一个单词而不是整个文本文件到 html 文件

如何更新显示的值而不是每次在 Python 中打印?

如何仅更改图像按钮内的图像而不是整个图像按钮

Memset 没有填满整个指针数组 c++

库函数(strcpy+memset函数精讲)

MSDOS 在屏幕上打印整个批处理文件而不是执行