【问题标题】:Replacing arrays access variables with the right integer type用正确的整数类型替换数组访问变量
【发布时间】:2014-09-11 21:35:33
【问题描述】:

我有使用 int 访问数组的习惯(尤其是在 for 循环中);然而,我最近发现我可能一直在“做错事”,而我的 x86 系统一直对我隐瞒真相。事实证明,当 sizeof(size_t) == sizeof(int) 时 int 很好,但是当在 sizeof(size_t) > sizeof(int) 的系统上使用时,它会导致额外的 mov 指令。 size_t 和 ptrdiff_t 似乎是我测试过的系统上的最佳方式,不需要额外的mov。

这是一个简短的例子

int vector_get(int *v,int i){ return v[i]; }

    > movslq    %esi, %rsi
    > movl  (%rdi,%rsi,4), %eax
    > ret

int vector_get(int *v,size_t i){ return v[i]; }

    > movl  (%rdi,%rsi,4), %eax
    > ret

好的,我已经修复了自己(现在使用 size_t 和 ptrdiff_t),现在我如何(希望不是手动)在我的代码中找到这些实例以便修复它们?

最近我注意到几个补丁,包括从 int 到 size_t 的更改,其中提到了 Clang。


我整理了一个表格,其中包含在每个实例中插入的额外指令,以显示“做错一切”的结果。

         char
        短
             int
无符号
         char

无符号
      短

无符号
            int
movsbq %sil, %rsi
movswq %si, %rsi
movslq %esi, %rsi

movzbl %sil, %esi


movzwl %si, %esi


movl %esi, %esi     kbd>

当
时不需要的移动操作表 访问具有“错误”类型的向量。

注意:long、long long、unsigned long、unsigned long long、size_t 和 ptrdiff_t 不需要额外的 mov* 操作(基本上任何 >= 最大对象大小,或 64 位上的 8 个字节参考系统)

编辑:

我想我可能有一个可用于修补 gcc 的存根,但我不知道如何绕过它的源来完成存根并添加适当的 -Wflag 位,并且像往常一样,编程中最难的部分是命名。 -Wunalinged-index?

gcc/c/c-typeck.c _______________________________________________

if (!swapped)
    warn_array_subscript_with_type_char (index);
> 
> if ( sizeof(index) < sizeof(size_t) ) 
>   warning_at (loc, OPT_Wunaligned_index,
>       "array index is smaller than size_t");

/* Apply default promotions *after* noticing character types.  */
index = default_conversion (index);

gcc/c-family/c.opt _____________________________________________

trigraphs
C ObjC C++ ObjC++
-trigraphs  Support ISO C trigraphs
> 
> Wunaligned-index
> C ObjC C++ ObjC++
> Warn about array indices smaller than size_t

undef
C ObjC C++ ObjC++ Var(flag_undef)
Do not predefine system-specific and GCC-specific macros

gcc/c-family/c-opts.c _________________________________________

case OPT_Wtrigraphs:
  cpp_opts->warn_trigraphs = value;
  break;
>
> case OPT_Wunaligned_index:
>   cpp_opts->warn_unaligned_index = value;
>

case OPT_Wundef:
  cpp_opts->warn_undef = value;
  break;

【问题讨论】:

  • 您是否使用int 作为索引,或者您的意思是什么?如果数组大于MAXINT,则int确实是错误的。或者你的意思是数组elements大于sizeof(int)?
  • 换句话说,你能给出一个你认为错误访问的代码示例吗?
  • 数组的大小根本不重要,只是系统的能力拥有超过 MAXINT 个元素。我会澄清的。
  • 与其乱用 MAXINT,我想你可以直接说sizeof(size_t) &gt; sizeof(int)。
  • @ecatmur - 简洁,我喜欢。固定的。还添加了几个示例。

标签: c arrays int static-analysis compiler-optimization


【解决方案1】:

clang 和 gcc 有 -Wchar-subscripts,但这只会帮助检测 char 下标类型。

您可能考虑修改 clang 或 gcc(无论哪个更容易在您的基础架构上构建)以扩大 -Wchar-subscripts 警告检测到的类型。如果这是一次性修复工作,这可能是最直接的解决方法。

否则你需要找到一个抱怨非size_t/ptrdiff_t下标的linter;我不知道有任何人有这个选项。

【讨论】:

  • 似乎 -Wchar-subscripts 只关心签名,但你可能会做一些事情。如果它已经在查看数组下标,那么它只需要检查if (sizeof(_subscript_)&lt;sizeof(size_t)) #warn ....
【解决方案2】:

movslq 指令将 long(又名 4 字节数量)符号扩展为 quad(又名 8 字节数量)。这是因为int 是有符号的,所以-1 的偏移量是0xffffffff 作为一个long。如果你只是零扩展(即没有movslq),这将是0x00000000ffffffff,又名4294967295,这可能不是你想要的。因此,编译器改为sign-扩展索引以产生0xffff...,也就是-1。

其他类型不需要额外操作的原因是,尽管其中一些已签名,但它们的大小仍然相同,为 8 字节。并且,由于二进制补码,0xffff... 可以解释为 -1 或 18446744073709551615,并且 64 位和仍然相同。

现在,通常情况下,如果您改用unsigned int,编译器通常必须插入一个零-extend,以确保上- 一半的寄存器不包含垃圾。但是,在 x64 平台上,这是隐式完成的; mov %eax,%esi 之类的指令会将 eax 中的任何 4 字节数量移动到 rsi 的低 4 字节并清除高 4 字节,从而有效地对数量进行零扩展。但是,鉴于您的帖子,编译器似乎无论如何都会插入mov %esi,%esi 指令,“只是为了确定”。

但是请注意,这种“自动零扩展”不是对于 1 字节和 2 字节数量的情况 - 必须手动进行零扩展。

【讨论】:

    猜你喜欢
    • 2021-04-24
    • 2014-12-08
    • 2021-07-04
    • 1970-01-01
    • 2023-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-26
    相关资源
    最近更新 更多