【问题标题】:Program crashes when trying to print a double (but other types are ok)尝试打印双精度时程序崩溃(但其他类型可以)
【发布时间】:2017-08-08 09:01:03
【问题描述】:

我正在尝试从汇编代码 (Linux x86-64) 调用 C 函数。在汇编代码中,类型由 3 LSB 编码。 C 函数的目的只是简单地打印键入的值。这是函数:

int print(long i) {
  int type = i & 7;
  long val = i >> 3;
  double c;
  switch(type) {
  case 0:
    printf("%d\n", val);
    break;
  case 1:
    if (!val)
      printf("false\n");
    else if (val == 1) 
      printf("true\n");
    else if (val == 2)
      printf("nil\n");
    break;
  case 2:
    printf("%s\n", (char*)(val + 8));
    break;
  case 6:
    c = *(double*) val;
    //printf("%d\n", (int)c); /* no seg fault */
    //printf("%f\n", 1.0); /* seg fault */
    printf("%f\n", c); /* seg fault */
    break;
  }
  return 1;
}

到目前为止,我一直在使用字符串指针没有任何问题,所以我知道这不是内存问题,但这是我第一次使用%xmm 寄存器,所以我可能在那里做错了。以下是相关的调用片段:

    movsd   _DB1(%rip), %xmm4
    movsd   _DB2(%rip), %xmm5
    addsd   %xmm5, %xmm4
    movsd   %xmm4, (%r12)
    leaq    6(, %r12, 8), %rdi
    addq    $8, %r12
    call    print
##### ... #####
    .data
_DB1:
    .double 2
_DB2:
    .double 3

%r12 指向堆中分配的内存。最奇怪的是,如果我在调用 printf 之前将双精度数转换为整数,则不会出现分段错误(取消注释案例 6 中的行并注释下面的行)并且它会得出正确的结果 (5)。在调试过程中,我发现在看不到代码的printf 函数的子程序中出现了分段错误。此外,即使我定义了一个随机双精度,我也无法打印它。

在这一点上,任何帮助将不胜感激,谢谢。

【问题讨论】:

  • 在案例 0 和案例 1 中,您将 val 作为常规变量处理,例如:printf("%d\n", val); 但在案例 6 中作为指针:c = *(double*) val; 我认为您的意思是:c = val;。此外,这段代码充满了幻数。您应该考虑为您的类型使用枚举。
  • 由于我将内存中双精度的位置传递给 C 函数,我需要将其转换为指针,如果我这样做 c = val 我不会得到双精度跨度>
  • 即使演员表是*(double *)&val,它也会有未定义的行为。不要这样做。
  • @Hadron:好吧,如果没有解释,提供的代码是看不到的。如果您传递地址,您会通过以下方式破坏它:long val = i >> 3; 或者您之前是否对它做过一些我们在这里没有看到的事情?
  • @Andre Kampling 我在这里加载地址leaq 6(, %r12, 8), %rdi,你可以看到我将它向左移动 3,然后添加 6,当我在参数中得到它时,我可以将它移回而不会丢失一部分

标签: c linux assembly x86-64


【解决方案1】:

代码片段的行为取决于如何将指向double 的指针和字符串编码为longi,以及其他考虑因素。它非常不便携。

您计算long val = i >> 3;,它对i 的负值具有实现定义的行为,并在稍后转换为(double*)(char*) 时生成具有01 高位的指针值。虽然指针表示中的一些高位在常见架构上被忽略,但依赖这种行为是不安全的,并且会在 32 位英特尔 PC 上失败。确保指针在 8 字节边界上对齐(至少)并在转换为 (double*)(char*) 之前屏蔽低位或以其他方式调整值可能是一个更好的主意。

如果long 小于void*,使用uintptr_t 而不是long 也会更便携,就像在64 位Windows 系统上一样。

【讨论】:

  • 有符号整数的右移是实现定义的,而不是未定义的。你会得到一个值,鼻恶魔是不可能的。 (不过,在几乎所有现有的 C 实现中,您都会得到算术右移,移动符号位的副本。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-22
  • 1970-01-01
  • 2012-04-10
  • 1970-01-01
  • 2021-11-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多