Каков правильный тип для индексов массива в C?
Какой тип для индекса массива в C99 следует использовать? Он должен работать на LP32, ILP32, ILP64, LP64, LLP64 и более. Это не обязательно должен быть тип C89.
Я нашел 5 кандидатов:
size_t
ptrdiff_t
intptr_t
/uintptr_t
int_fast*_t
/uint_fast*_t
int_least*_t
/uint_least*_t
Существует простой код, чтобы лучше проиллюстрировать проблему. Какой тип лучше всего подходит для i
а также j
в этих двух конкретных циклах. Если есть веская причина, два разных типа тоже подойдут.
for (i=0; i<imax; i++) {
do_something(a[i]);
}
/* jmin can be less than 0 */
for (j=jmin; j<jmax; j++) {
do_something(a[j]);
}
PS В первой версии вопроса я забыл об отрицательных показателях.
PPS Я не собираюсь писать компилятор C99. Однако любой ответ от программиста компилятора был бы очень ценным для меня.
Подобный вопрос:
- size_t против uintptr_t
Контекст этого вопроса, если отличается, хотя.
8 ответов
Я почти всегда использую size_t
для индексов массива / счетчиков цикла. Конечно, есть некоторые особые случаи, когда вам могут потребоваться смещения со знаком, но в целом использование типа со знаком имеет много проблем:
Наибольший риск заключается в том, что если вы передали вызывающему абоненту огромный размер / смещение, которое рассматривает вещи как неподписанные (или если вы прочитали это из файла с ошибочно доверенными данными), вы можете интерпретировать его как отрицательное число и не поймать, что оно за границами. Например if (offset<size) array[offset]=foo; else error();
напишу где-то не должно.
Другая проблема заключается в возможности неопределенного поведения с целочисленным переполнением со знаком. Независимо от того, используете ли вы неподписанную или подписанную арифметику, существуют проблемы переполнения, о которых нужно знать и проверять, но лично я считаю, что с неподписанным поведением гораздо легче справиться.
Еще одна причина использовать арифметику без знака (в общем) - иногда я использую индексы как смещения в массиве битов и хочу использовать%8 и /8 или%32 и /32. С подписанными типами это будут фактические операции деления. С unsigned можно генерировать ожидаемые побитовые и / битовые операции.
Я думаю, что вы должны использовать ptrdiff_t
по следующим причинам
- Индексы могут быть отрицательными (при этом все типы без знака, в том числе
size_t
не может быть и речи) - Тип
p2 - p1
являетсяptrdiff_t
, Типi
в обратном направлении,*(p1 + i)
, должен быть такого типа тоже (обратите внимание, что*(p + i)
эквивалентноp[i]
)
Так как тип sizeof(array)
(а также malloc
аргумент) size_t
и массив не может содержать больше элементов, чем его размер, из этого следует, что size_t
может использоваться для индекса массива.
РЕДАКТИРОВАТЬ Этот анализ для массивов на основе 0, что является распространенным случаем. ptrdiff_t
будет работать в любом случае, но для переменной индекса немного странно иметь тип разности указателей.
Если вы начнете с 0
используйте size_t, потому что этот тип должен иметь возможность индексировать любой массив:
sizeof
возвращает его, поэтому недопустимо, чтобы массив имел большеsize_t
элементыmalloc
принимает это как аргумент, как упомянуто Амноном
Если вы начнете ниже нуля, то переключитесь на старт с нуля и используйте size_t
, который гарантированно работает по причинам, изложенным выше. Так что замените:
for (j = jmin; j < jmax; j++) {
do_something(a[j]);
}
с:
int *b = &a[jmin];
for (size_t i = 0; i < (jmax - jmin); i++) {
do_something(b[i]);
}
Почему бы не использовать:
ptrdiff_t: максимальное значение, которое это представляет, может быть меньше, чем максимальное значение
size_t
,Это упоминается в cppref, и возможность неопределенного поведения, если массив слишком велик, предлагается в C99 6.5.5/9:
Когда вычтены два указателя, оба должны указывать на элементы одного и того же объекта массива или один после последнего элемента объекта массива; Результатом является разница индексов двух элементов массива. Размер результата определяется реализацией, а его тип (целочисленный тип со знаком) ptrdiff_t определен в заголовке. Если результат не может быть представлен в объекте этого типа, поведение не определено.
Из любопытства,
intptr_t
также может быть больше, чемsize_t
на архитектуре сегментированной памяти: /questions/11674223/sizet-protiv-uintptrt/11674239#11674239GCC также накладывает дополнительные ограничения на максимальный размер объектов статического массива. Какой максимальный размер массива в C?
uintptr_t: я не уверен. Так что я бы просто использовал
size_t
потому что я более уверен:-)
Мой выбор: ptrdiff_t
Многие проголосовали за ptrdiff_t
, но некоторые говорят, что странно индексировать с использованием разностного типа указателя. Для меня это имеет смысл: индекс массива - это отличие от исходного указателя.
Некоторые также сказали, что size_t
правильно, потому что это предназначено для хранения размера. Однако, как прокомментировали некоторые: это размер в байтах, и поэтому он обычно может содержать значения, в несколько раз превышающие максимально возможный индекс массива.
Я использую unsigned int
, (хотя я предпочитаю стенографию unsigned
)
В С99 unsigned int
гарантированно сможет индексировать любой переносимый массив. Гарантируется поддержка только массивов размером 65'535 байт или меньше, а максимальный unsigned int
значение не менее 65'535.
Из открытого проекта WG14 N1256 стандарта C99:
5.2.4.1 Пределы перевода
Реализация должна иметь возможность переводить и выполнять по крайней мере одну программу, которая содержит по крайней мере один экземпляр каждого из следующих ограничений: (Реализации должны по возможности избегать наложения фиксированных пределов перевода).
(...)
- 65535 байт в объекте (только в размещенной среде)
(...)
5.2.4.2 Численные ограничения
Реализация должна документировать все ограничения, указанные в этом подпункте, которые указаны в заголовках
<limits.h>
а также<float.h>
, Дополнительные ограничения указаны в<stdint.h>
,5.2.4.2.1 Размеры целочисленных типов
<limits.h>
Значения, приведенные ниже, должны быть заменены постоянными выражениями, подходящими для использования в
#if
директивы предварительной обработки. Более того, кромеCHAR_BIT
а такжеMB_LEN_MAX
следующее должно быть заменено выражениями того же типа, что и выражение, являющееся объектом соответствующего типа, преобразованным в соответствии с целочисленными преобразованиями. Их определяемые реализацией значения должны быть равны или больше по величине (абсолютное значение) показанным с тем же знаком.(...)
- максимальное значение для объекта типа
unsigned int
UINT_MAX
65535 // 2^16 - 1
В ANSI C максимальный размер переносимого массива на самом деле составляет всего 32 767 байт, поэтому даже подписанный int
будет делать, который имеет максимальное значение не менее 32 767 (Приложение A.4).
Из §2.2.4 проекта C89:
2.2.4.1 Пределы перевода
Реализация должна иметь возможность переводить и выполнять по крайней мере одну программу, которая содержит по крайней мере один экземпляр каждого из следующих ограничений: (Реализации должны по возможности избегать наложения фиксированных пределов перевода).
(...)
- 32767 байт в объекте (только в размещенной среде)
(...)
2.2.4.2 Численные ограничения
Соответствующая реализация должна документировать все ограничения, указанные в этом разделе, которые должны быть указаны в заголовках.
<limits.h>
а также<float.h>
,"Размеры интегральных типов
<limits.h>
"Значения, приведенные ниже, должны быть заменены константными выражениями, подходящими для использования в директивах предварительной обработки #if. Их определяемые реализацией значения должны быть равны или больше по величине (абсолютное значение) показанным с тем же знаком.
(...)
- максимальное значение для объекта типа int
INT_MAX
+32767
В вашей ситуации я бы использовал ptrdiff_t
, Дело не только в том, что признаки могут быть отрицательными. Возможно, вы захотите отсчитать до нуля, и в этом случае подписанные типы приводят к неприятной, тонкой ошибке:
for(size_t i=5; i>=0; i--) {
printf("danger, this loops forever\n);
}
Этого не произойдет, если вы используете ptrdiff_t
или любой другой подходящий подписанный тип. В системах POSIX вы можете использовать ssize_t
,
Лично я часто просто использую int
даже если это, возможно, не правильная вещь.
Если вы заранее знаете максимальную длину вашего массива, вы можете использовать
int_fast*_t / uint_fast*_t
int_least*_t / uint_least*_t
Во всех остальных случаях я бы рекомендовал использовать
size_t
или же
ptrdiff_t
в зависимости от погоды вы хотите разрешить отрицательные показатели.
С помощью
intptr_t / uintptr_t
было бы также безопасно, но иметь немного другую семантику.
Я обычно использую size_t
для смещений массива, но если вам нужна отрицательная индексация массива, используйте int
. Он может обращаться к массиву максимального размера, гарантированному C89 (32767 байт).
Если вы хотите получить доступ к массивам максимального размера, гарантированного C99 (65535 байт), используйте unsigned
.
См. Предыдущие версии для доступа к массивам, разрешенного, но не гарантированного C.