Каков правильный тип индексов массивов в 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]);
}

P.S. В первой версии вопроса я забыл об отрицательных индексах.

P.P.S. Я не собираюсь писать компилятор C99. Однако любой ответ от программиста-компилятора был бы очень ценным для меня.

Аналогичный вопрос:

  • size_t vs. uintptr_t
    Контекст этого вопроса, если отличается, однако.

Ответ 1

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

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

Другая проблема заключается в возможности поведения undefined со знаком целочисленного переполнения. Используете ли вы неподписанную или подписанную арифметику, есть проблемы с переполнением, которые нужно знать и проверять, но лично я считаю, что неподписанное поведение намного легче справиться.

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

Ответ 2

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

  • Индексы могут быть отрицательными. Поэтому для общего утверждения все неподписанные типы, включая size_t, не подходят.
  • Тип p2 - p1 является ptrdiff_t. Если i == p2 - p1, то вы сможете вернуть p2 к p2 == p1 + i. Обратите внимание, что *(p + i) эквивалентен p[i].
  • В качестве другого указания для этого "общего типа индекса" тип индекса, который используется разрешением перегрузки, когда встроенный operator[] (например, для указателя) конкурирует с предоставленным пользователем operator[] (например, векторным), является именно это (http://eel.is/c++draft/over.built#16):

    Для каждого cv-квалифицированного или cv-неквалифицированного типа объекта T существуют операторные функции-кандидаты вида

    T*      operator+(T*, std::ptrdiff_t);
    T&      operator[](T*, std::ptrdiff_t);
    T*      operator-(T*, std::ptrdiff_t);
    T*      operator+(std::ptrdiff_t, T*);
    T&      operator[](std::ptrdiff_t, T*);
    

ОБНОВЛЕНИЕ: Если у вас действительно большой массив или указатель на действительно большую часть памяти, то мой "общий тип индекса" не обрезает его, так как тогда не гарантируется, что вы можете вычесть адрес первого элемента из последнего элемента адрес. Тогда ответ @Ciro должен быть использован fooobar.com/questions/17389/.... Лично я стараюсь избегать использования беззнаковых типов из-за их неспособности представлять отрицательные крайние значения (например, конечные значения цикла при повторении в обратном порядке), но это своего рода религиозные дебаты (хотя я не одинок в этом лагере), В случаях, когда требуется использование беззнакового типа, я, конечно, должен отложить свою религию в сторону.

Ответ 3

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

ИЗМЕНИТЬ Этот анализ предназначен для массивов на основе 0, что является общим случаем. ptrdiff_t будет работать в любом случае, но немного странно, если индексная переменная имеет тип разности указателей.

Ответ 4

size_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 в архитектуре с сегментированной памятью: fooobar.com/questions/17620/...

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

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

Смотрите также:

Ответ 5

Мой выбор: ptrdiff_t

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

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

Ответ 6

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

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

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

  • size_t

или

  • ptrdiff_t

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

Используя

  • intptr_t / uintptr_t

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

Ответ 7

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

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

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

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

Ответ 8

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

(...)

  • максимальное значение для объекта типа 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

Ответ 9

Я обычно использую size_t для смещения массива, но если вы хотите использовать отрицательную индексацию массива, используйте int. Он может обращаться к массиву максимального размера, гарантированному C89 (32767 байт).

Если вы хотите получить доступ к массивам максимального размера, гарантируемого C99 (65535 байт), используйте unsigned.

См. предыдущие версии для доступа к массивам, разрешенным, но не гарантированным, C.