Каков правильный тип для индексов массива в C? - PullRequest
33 голосов
/ 04 июля 2010

Какой тип индекса массива в 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.Однако любой ответ программиста компилятора будет для меня очень ценным.

Подобный вопрос:

Ответы [ 8 ]

34 голосов
/ 04 июля 2010

Я думаю, вы должны использовать ptrdiff_t по следующим причинам

  • Индексы могут быть отрицательными (поэтому все типы без знака, включая size_t, исключены)
  • Тип p2 - p1 равен ptrdiff_t.Тип i в обратной вещи, *(p1 + i), должен быть таким же типом (обратите внимание, что *(p + i) эквивалентен p[i])
15 голосов
/ 07 июля 2010

Я почти всегда использую size_t для индексов массивов / счетчиков циклов.Конечно, есть некоторые особые случаи, когда вам может потребоваться смещение со знаком, но в целом использование типа со знаком имеет много проблем:

Самый большой риск состоит в том, что если вы передали вызывающему абоненту огромный размер / смещениерассматривая вещи как неподписанные (или если вы читаете их из ошибочно доверенного файла), вы можете интерпретировать это как отрицательное число и не поймать, что оно выходит за пределы.Например, if (offset<size) array[offset]=foo; else error(); напишет куда-то, что не должно.

Другая проблема - это возможность неопределенного поведения со целочисленным переполнением со знаком.Независимо от того, используете ли вы беззнаковую или подписанную арифметику, существуют проблемы переполнения, о которых нужно знать и проверять, но лично я считаю, что с неподписанным поведением гораздо проще разобраться.) - иногда я использую индексы как смещения в битовом массиве и хочу использовать% 8 и / 8 или% 32 и / 32.С подписанными типами это будут фактические операции деления.С unsigned можно генерировать ожидаемые операции побитового и / битового смещения.

12 голосов
/ 04 июля 2010

Поскольку тип sizeof(array) (и аргумент malloc) равен size_t, а массив не может содержать больше элементов, чем его размер, из этого следует, что size_t может использоваться для индекса массива.

РЕДАКТИРОВАТЬ Этот анализ для массивов на основе 0, что является распространенным случаем.ptrdiff_t будет работать в любом случае, но для переменной индекса немного странно иметь тип разности указателей.

6 голосов

Если вы начинаете с 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 наархитектура сегментированной памяти: https://stackoverflow.com/a/1464194/895245

    GCC также накладывает дополнительные ограничения на максимальный размер объектов статического массива: Какой максимальный размер массива в C?

  • uintptr_t : Я не уверен.Так что я бы просто использовал size_t, потому что я более уверен: -)

1 голос
/ 30 июня 2018

Мой выбор: ptrdiff_t

Многие проголосовали за ptrdiff_t, но некоторые сказали, что странно индексировать с использованием разностных указателей.Для меня это имеет смысл: индекс массива отличается от исходного указателя.

Некоторые также говорят, что size_t правильно, потому что он предназначен для хранения размера.Однако, как прокомментировали некоторые: это размер в байтах, и поэтому он обычно может содержать значения, в несколько раз превышающие максимально возможный индекс массива.

1 голос
/ 18 февраля 2018

Я использую unsigned int. (хотя я предпочитаю сокращение unsigned)

В C99 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, следующее должно быть заменено выражениями того же типа, что и выражение, являющееся объектом соответствующего типа, преобразованным в соответствии с целочисленными повышениями. Их значения, определенные реализацией, должны быть равны или больше по величине (абсолютное alue) к показанным с тем же знаком.

(...)

  • максимальное значение для объекта типа unsigned int UINT_MAX 65535 // 2 ^ 16 - 1

В C89 максимальный размер переносимого массива на самом деле составляет всего 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
1 голос
/ 11 марта 2015

В вашей ситуации я бы использовал ptrdiff_t. Дело не только в том, что признаки могут быть отрицательными. Возможно, вы захотите отсчитать до нуля, и в этом случае подписанные типы приводят к неприятной, тонкой ошибке:

for(size_t i=5; i>=0; i--) {
  printf("danger, this loops forever\n);
}

Этого не произойдет, если вы используете ptrdiff_t или любой другой подходящий тип со знаком. В системах POSIX вы можете использовать ssize_t.

Лично я часто просто использую int, хотя это, возможно, и не совсем правильно.

1 голос
/ 06 июля 2010

Если вы заранее знаете максимальную длину вашего массива, вы можете использовать

  • int_fast*_t / uint_fast*_t
  • int_least*_t / uint_least*_t

Во всех остальныхя бы рекомендовал использовать

  • size_t

или

  • ptrdiff_t

в зависимости отЕсли вы хотите разрешить отрицательные индексы.

Использование

  • intptr_t / uintptr_t

также будет безопасным, но имеет немного другую семантику.

...