Замена переменных доступа к массивам с правильным целым типом
У меня была привычка использовать int для доступа к массивам (особенно для циклов); однако недавно я обнаружил, что я, возможно, "делаю-все-неправильно", и моя система x86 скрывала от меня правду. Оказывается, int отлично, когда sizeof(size_t) == sizeof(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
Table of unwanted move operations when
accessing vectors with "wrong" type.
Примечание: long
, long long
, unsigned long
, unsigned long long
, size_t
и ptrdiff_t
не требуют дополнительной операции mov * (в основном ничего >= наибольший размер объекта или 8 байтов на 64-битной эталонная система)
Edit:
Я думаю, что у меня может быть работоспособный заглушка для исправления gcc, но я не знаю своего пути по его источнику, чтобы заполнить заглушку и добавить соответствующие бит -Wflag, и, как обычно, самая сложная часть программирования - это именование. -Wunalinged-индекс?
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;
Ответы
Ответ 1
clang и gcc имеют -Wchar-subscripts
, но это поможет обнаружить только типы индексов char
.
Возможно, вы захотите изменить clang или gcc (в зависимости от того, что проще построить на вашей инфраструктуре), чтобы расширить типы, обнаруженные предупреждением -Wchar-subscripts
. Если это одношаговое усилие исправить, это может быть самым простым способом сделать это.
В противном случае вам нужно будет найти linter, который жалуется на подписку non size_t
/ptrdiff_t
; Я не знаю ни одного, у кого есть этот вариант.
Ответ 2
Знак команды movslq
расширяет a long
(aka 4-байтовое количество) до quad
(также 8-байтовое количество). Это связано с тем, что int
подписан, поэтому смещение, т.е. -1
, равно 0xffffffff
как длинное. Если бы вам нужно было просто нулевое расширение (т.е. Не иметь movslq
), это будет 0x00000000ffffffff
, aka 4294967295
, что, вероятно, не то, что вы хотите. Таким образом, компилятор вместо sign-расширяет индекс, чтобы получить 0xffff...
, aka -1
.
Причина, по которой другие типы не требуют дополнительной операции, состоит в том, что, несмотря на то, что некоторые из них подписаны, они по-прежнему равны 8 байтам. И, благодаря двум дополнениям, 0xffff...
можно интерпретировать как либо -1
, либо 18446744073709551615
, а 64-разрядная сумма будет по-прежнему одинаковой.
Теперь, как правило, если вы должны использовать unsigned int
, компилятору обычно нужно будет вставить нуль-расширение вместо этого, чтобы убедиться, что верхняя половина регистра не содержит мусора. Однако на платформе x64 это делается неявно; инструкция, такая как mov %eax,%esi
, будет перемещать любое количество из 4 байтов в eax
в нижние 4 байта rsi
и очистить верхнюю 4, эффективно увеличивая количество. Но, учитывая ваши сообщения, компилятор, похоже, вставляет инструкцию mov %esi,%esi
в любом случае, "просто чтобы быть уверенным".
Обратите внимание, однако, что это "автоматическое нулевое расширение" не имеет значения для 1- и 2-байтовых величин - они должны быть вручную увеличены до нуля.