用正确的整数类型替换数组访问变量

4
我一直习惯使用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
        short
             int
unsigned
         char

unsigned
        short

unsigned
            int
movsbq %sil, %rsi
movswq %si, %rsi
movslq %esi, %rsi

movzbl %sil, %esi  


movzwl %si, %esi  


movl %esi, %esi    


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

注意: longlong longunsigned longunsigned long longsize_tptrdiff_t不需要额外的mov*操作(基本上是任何>=最大对象大小或在64位参考系统上为8字节)

编辑:

我想我可能已经有了一个可行的gcc补丁桩,但我不知道如何在其源代码中完成桩并添加适当的-W标志位,而通常编程中最困难的部分就是命名东西。-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 _____________________________________________

以上代码是指向GCC编译器中c-family/c.opt文件的路径。该文件与C语言及其衍生语言(如C++)相关,可能包含编译器选项。
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 __________________________________________

在这个文件中,定义了处理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 的确是不正确的。或者你是想说数组元素大于 sizeof(int) - Rudy Velthuis
1
换句话说,你能给出一个你认为是错误访问的代码示例吗? - Rudy Velthuis
数组的大小并不重要,重要的是系统具有超过MAXINT元素的能力。我会澄清。 - technosaurus
1
与其在MAXINT上纠结,我认为你可以说“sizeof(size_t) > sizeof(int)”。 - ecatmur
@ecatmur - 简洁明了,我喜欢。已修复。还添加了几个示例。 - technosaurus
2个回答

1
clang和gcc都有-Wchar-subscripts,但这只能帮助检测char下标类型。你可能需要修改clang或gcc(哪个更容易在您的基础设施上构建)以扩大由-Wchar-subscripts警告检测到的类型。如果这是一个一次性修复的工作,那么这可能是最直接的方法。否则,您需要找到一个抱怨非size_t/ptrdiff_t下标的linter;我不知道有任何具有该选项的linter。

好像-Wchar-subscripts只关心符号,但你可能正在做某些事情。如果已经查看了数组下标,那么它只需要进行检查if (sizeof(_subscript_)<sizeof(size_t)) #warn .... - technosaurus

0

movslq指令将long(即4字节)符号扩展为quad(即8字节)。这是因为int是有符号的,所以偏移量例如-1在长整型中表示为0xffffffff。如果您只是零扩展它(即没有movslq),那么这将是0x00000000ffffffff,也就是4294967295,这可能不是您想要的结果。因此,编译器会将索引进行符号扩展,得到0xffff...,即-1

其他类型不需要额外的操作的原因是,尽管其中一些是有符号的,但它们仍然具有相同的8字节大小。而且,由于采用了二进制补码,0xffff...可以被解释为-118446744073709551615,64位和仍然相同。

通常情况下,如果您使用的是unsigned int,编译器通常会插入一个零扩展,以确保寄存器的高半部分不包含垃圾。但在x64平台上,这是隐式完成的;例如,mov %eax,%esi这样的指令将eax中的任何4字节数量移动到 rsi 的低4字节中,并清除上4字节,从而 effectively 零扩展该数量。但是,根据您的帖子,编译器似乎无论如何都会插入 mov %esi,%esi 指令,“只是为了确保”。

但需要注意的是,对于1字节和2字节的量,这种"自动零扩展"是不适用的-必须手动进行零扩展。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接