int vs size_t на 64-битной - PullRequest
       52

int vs size_t на 64-битной

16 голосов
/ 26 марта 2010

Код переноса с 32 бита на 64 бита. Много мест с

int len = strlen(pstr);

Все они теперь генерируют предупреждения, потому что strlen () возвращает size_t, который является 64-битным, а int все еще 32-битным. Поэтому я заменил их на

size_t len = strlen(pstr);

Но я только что понял, что это небезопасно, так как size_t без знака и может рассматриваться как подписанный кодом (на самом деле я столкнулся с одним случаем, когда это вызвало проблему, спасибо, модульные тесты!) *

Слепой разыгрывающий возвращение к (int) чувствует себя грязным. А может, не стоит?
Таким образом, вопрос: есть ли элегантное решение для этого? У меня, вероятно, есть тысяча строк кода в кодовой базе; Я не могу вручную проверить каждый из них, и тестовое покрытие в настоящее время находится где-то между 0,01 и 0,001%.

Ответы [ 6 ]

7 голосов
/ 28 мая 2010

Некоторое время назад я опубликовал короткую заметку о подобных проблемах в своем блоге, и короткий ответ:

Всегда используйте правильные целочисленные типы C ++

Длинный ответ: При программировании на C ++ рекомендуется использовать правильные целочисленные типы, соответствующие конкретному контексту. Немного строгости всегда окупается. Нередко наблюдается тенденция игнорировать целочисленные типы, определенные как специфичные для стандартных контейнеров, а именно size_type. Он доступен для номера стандартного контейнера, такого как std :: string или std :: vector. Такое невежество может легко отомстить.

Ниже приведен простой пример неверно используемого типа для получения результата функции std :: string :: find. Я совершенно уверен, что многие ожидают, что здесь нет ничего плохого в неподписанном int. Но на самом деле это просто ошибка. Я запускаю Linux на 64-битной архитектуре, и когда я компилирую эту программу как есть, она работает как положено. Однако, когда я заменяю строку в строке 1 на abc, она все равно работает, но не так, как ожидалось: -)

#include <iostream>
#include <string>
using namespace std;
int main()
{
  string s = "a:b:c"; // "abc" [1]
  char delim = ':';
  unsigned int pos = s.find(delim);
  if(string::npos != pos)
  {
    cout << delim << " found in " << s << endl;
  }
}

Исправить очень просто. Просто замените unsigned int на std :: string :: size_type. Эту проблему можно избежать, если кто-то, кто написал эту программу, позаботится об использовании правильного типа. Не говоря уже о том, что программа будет переносимой сразу.

Я сталкивался с подобными проблемами довольно много раз, особенно в коде, написанном бывшими программистами C, которые не любят носить дуло строгости, которую требует система типов C ++. Приведенный выше пример является тривиальным, но я считаю, что он хорошо представляет корень проблемы.

Я рекомендую блестящую статью 64-битная разработка , написанную Андреем Карповым, где вы можете найти гораздо больше по теме.

5 голосов
/ 26 марта 2010

В качестве компромисса вы можете использовать ssize_t (если доступно). Подделайте, если нет, используя long long, int_fast64_t, intmax_t, или используйте заголовок переноса платформы, который позволяет указать подходящий тип для платформы. ssize_t в POSIX не стандарт C или C ++, но если вы когда-нибудь попадете на платформу, у которой нет типа со знаком того же размера, что и size_t, тогда я сочувствую.

Приведение к int является почти безопасным (при условии 32-битного int на вашей 64-битной платформе, что кажется разумным), поскольку длина строки вряд ли будет больше 2 ^ 31 байта. Приведение к большему типу со знаком еще безопаснее. Клиенты, которые могут позволить себе 2 ^ 63 байта памяти - это то, что в торговле называют «хорошей проблемой»; -)

Конечно, вы можете проверить это:

size_t ulen = strlen(pstr);
if (ulen > SSIZE_MAX) abort(); // preferably trace, log, return error, etc.
ssize_t len = (ssize_t) ulen;

Конечно, есть накладные расходы, но если у вас есть 1000 экземпляров, то все они не могут быть критичными для производительности. Для тех, которые (если таковые имеются), вы можете выполнить работу, чтобы выяснить, действительно ли подпись len имеет значение. Если это не так, переключитесь на size_t. Если это произойдет, переписать или просто рискнуть никогда не встречать объект, который нелепо огромен. Исходный код почти наверняка сделал бы неправильную вещь на 32-битной платформе, если бы len был отрицательным в результате strlen, возвращающего значение, большее, чем INT_MAX.

5 голосов
/ 26 марта 2010

Установка предупреждений компилятора на максимальный уровень должна дать вам хороший отчет о каждом неправильном преобразовании знака. В gcc, '-Wall -Wextra' должен делать.

Вы также можете использовать статический анализатор кода, такой как cppcheck, чтобы проверить, все ли правильно.

4 голосов
/ 26 марта 2010

Вы можете использовать ssize_t (подписанный вариант size_t).

1 голос
/ 09 июня 2011

Если ваш компилятор поддерживает c ++ 0x:

auto len = strlen(pstr);
1 голос
/ 26 марта 2010

В большинстве случаев вы можете безопасно обращаться с подписанным site_t. Беззнаковый size_t будет рассматриваться как отрицательный, только когда он (или промежуточные результаты в выражениях) больше, чем 2 ^ 31 (для 32-разрядных) или 2 ^ 63 для 64-разрядных.

* +1002 * UPDATE: Извините, size_t будет небезопасен в конструкциях типа while ( (size_t)t >=0 ). Поэтому правильный ответ - использовать ssize_t.
...