【问题标题】:What does this x86-64 addq instruction mean, which only have one operand? (From CSAPP book 3rd Edition)这个 x86-64 addq 指令是什么意思,它只有一个操作数? (来自 CSAPP 第 3 版)
【发布时间】:2018-06-26 12:50:29
【问题描述】:

在以下说明中,addq 是如何工作的?它只有一个操作数,书中声称它递增 %rdx,但 %rdx 不在此指令中。我很困惑...

这是来自《Computer Systems A Programmers Perspective, 3rd Edition》一书。

【问题讨论】:

  • 那是书中的一个错误。下一行也是写回%eax(即i)而不是%edx。这两条指令应该是addq $1, %rdx; movq %rdx, cnt(%rip)。或者整个块可以替换为addq $1, cnt(%rip)
  • @Jester 非常感谢!你的指示更有意义!我很喜欢这本 CSAPP 的书,但是这样的错误太烦人了!
  • 请附上书名,以便其他有相同问题的人找到。
  • @RaymondChen 好建议,我在标题和正文中添加了书名。 :)
  • 顺便说一句,这本教科书的全球版中的练习题似乎被出版商替换成了废话。 csapp.cs.cmu.edu/3e/errata.html。例如CS:APP example uses idivq with two operands?

标签: assembly x86 computer-science x86-64


【解决方案1】:

正如@Jester 在评论中指出的那样。这确实是一个错误。 我实际上输入了程序并在 linux 上使用 gcc 编译它。以下是结果。

C 程序:badcnt.c

/*
 * badcnt.c - An improperly synchronized counter program
 */
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>

void *thread(void *vargp);  /* Thread routine prototype */

/* Global shared variable */
volatile int cnt = 0; /* Counter */

int main(int argc, char **argv)
{
  int niters;
  pthread_t tid1, tid2;

  /* Check input argument */
  if (argc != 2) {
    printf("usage: %s <niters>\n", argv[0]);
    exit(0);
  }
  niters = atoi(argv[1]);

  /* Create threads and wait for them to finish */
  pthread_create(&tid1, NULL, thread, &niters);
  pthread_create(&tid2, NULL, thread, &niters);
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);

  /* Check result */
  if (cnt != (2 * niters))
    printf("BOOM! cnt=%d\n", cnt);
  else
    printf("OK cnt=%d\n", cnt);
  exit(0);
}

/* Thread routine */
void *thread(void *vargp)
{
  int i, niters = *((int *)vargp);

  for (i = 0; i < niters; i++)
    cnt++;

  return NULL;
}

使用 gcc 6.3.0 编译

$ gcc -pthread -Og -S badcnt.c

下面是badcnt.s中的内容

        .file   "badcnt.c"
        .text
        .globl  thread
        .type   thread, @function
thread:
.LFB20:
        .cfi_startproc
        movl    (%rdi), %ecx
        movl    $0, %edx
        jmp     .L2
.L3:
        movl    cnt(%rip), %eax
        addl    $1, %eax
        movl    %eax, cnt(%rip)
        addl    $1, %edx
.L2:
        cmpl    %ecx, %edx
        jl      .L3
        movl    $0, %eax
        ret
        .cfi_endproc
.LFE20:
        .size   thread, .-thread
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "usage: %s <niters>\n"
.LC1:
        .string "BOOM! cnt=%d\n"
.LC2:
        .string "OK cnt=%d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB19:
        .cfi_startproc
        subq    $40, %rsp
        .cfi_def_cfa_offset 48
        cmpl    $2, %edi
        je      .L5
        movq    (%rsi), %rsi
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        movl    $0, %edi
        call    exit
.L5:
        movq    8(%rsi), %rdi
        movl    $10, %edx
        movl    $0, %esi
        call    strtol
        movl    %eax, 28(%rsp)
        leaq    28(%rsp), %rcx
        movl    $thread, %edx
        movl    $0, %esi
        leaq    16(%rsp), %rdi
        call    pthread_create
        leaq    28(%rsp), %rcx
        movl    $thread, %edx
        movl    $0, %esi
        leaq    8(%rsp), %rdi
        call    pthread_create
        movl    $0, %esi
        movq    16(%rsp), %rdi
        call    pthread_join
        movl    $0, %esi
        movq    8(%rsp), %rdi
        call    pthread_join
        movl    28(%rsp), %eax
        addl    %eax, %eax
        movl    cnt(%rip), %edx
        cmpl    %edx, %eax
        je      .L6
        movl    cnt(%rip), %esi
        movl    $.LC1, %edi
        movl    $0, %eax
        call    printf
.L7:
        movl    $0, %edi
        call    exit
.L6:
        movl    cnt(%rip), %esi
        movl    $.LC2, %edi
        movl    $0, %eax
        call    printf
        jmp     .L7
        .cfi_endproc
.LFE19:
        .size   main, .-main
        .globl  cnt
        .bss
        .align 4
        .type   cnt, @object
        .size   cnt, 4
cnt:
        .zero   4
        .ident  "GCC: (GNU) 6.3.0"
        .section        .note.GNU-stack,"",@progbits

所以它证实了本书的错误。

【讨论】:

  • 奇怪的是-Og 不使用addl $1, cnt(%rip)。在现代英特尔 CPU 上,这比单独的加载/添加/存储执行效率略高。 (有趣的是,more efficiently than incl $1, cnt(%rip) because an add uop can micro-fuse a load。)也许作者的意思是写inc %edx 而不是add?否则它只是一个复制/粘贴编辑错误,因为我认为这应该是真正的编译器输出。)
  • 哦,nvm,这并不奇怪,这正是 gcc 对 volatile 的一贯做法。我们从 Godbolt 编译器资源管理器上的 gcc -O3 得到相同的东西:godbolt.org/g/9AuFHq。 (是的,你需要volatile 来阻止它在没有循环的情况下将循环编译为addl %reg, cnt(%rip),除非你禁用了优化。)Clang 没有volatile 的错过优化,但它使用@ 987654336@ 在展开循环中,而不是 addl
  • @PeterCordes 非常感谢您提供的信息! codbolt.org 编译器资源管理器看起来很酷~ 收藏了! :)
猜你喜欢
  • 1970-01-01
  • 2013-06-22
  • 1970-01-01
  • 2021-12-30
  • 1970-01-01
  • 1970-01-01
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多