2015-06-27 4 views
2

Я немного смущен как использовать size_t, когда в программе присутствуют другие типы данных, такие как int, unsigned long int и unsigned long long int. Я стараюсь проиллюстрировать свою путаницу минимально. Представьте себе программу, в которой я используюКогда отливать size_t

void *calloc(size_t nmemb, size_t size) 

выделить массив (одномерный или многомерный). Пусть вызов calloc() будет зависеть от nrow и sizeof(unsigned long int). sizeof(unsigned long int) явно отлично, потому что он возвращает size_t. Но пусть nrow будет таким, чтобы он имел тип unsigned long int. Что мне делать в таком случае? Делать ли я nrow в вызове calloc() от unsigned long int до size_t?

Другой случай будет

char *fgets(char *s, int size, FILE *stream) 

fgets() ожидает типа int в качестве второго параметра. Но что, если я передам ему массив, допустим, save, так как это первый параметр и использовать sizeof(save), чтобы передать ему размер массива? Я передаю вызов sizeof() в int? Это было бы опасно, так как int не гарантированно удерживает все возможные доходы от sizeof().

Что делать в этих двух случаях? Бросить или просто игнорировать возможные предупреждения от таких инструментов, как splint?

Вот пример относительно calloc() (я явно опускаем проверки ошибок для ясности!):

long int **arr; 
unsigned long int mrow; 
unsigned long int ncol; 

arr = calloc(mrow, sizeof(long int *)); 

for(i = 0; i < mrow; i++) { 
     arr[i] = calloc(ncol, sizeof(long int)); 
} 

Вот пример для fgets() (обработка ошибок снова опущено для ясности!):

char save[22]; 
char *ptr_save; 
unsigned long int mrow 
if (fgets(save, sizeof(save), stdin) != NULL) { 
     save[strcspn(save, "\n")] = '\0'; 
     mrow = strtoul(save, &ptr_save, 10); 
} 
+0

В чем разница между 'SizeOf (без знака длинного межд)' и 'SizeOf (длинный межд)'? И 'size_t' фактически' unsigned'. –

+0

Я забыл набрать 'unsigned'. –

+1

Включить предупреждения компилятора и '-Wconversion' (gcc). Это должно дать вам подсказки, где у вас есть потенциальные проблемы с усечением или знаком. Жонглирование с помощью подписанных и беззнаковых целых чисел иногда довольно сложно. – Olaf

ответ

0

В общем, вы правы, вы не должны игнорировать предупреждения! И вообще, если можно, вы должны уклоняться от явных бросков, потому что они могут сделать ваш код менее надежным, или предупреждение о молчании, которое действительно пытается рассказать вам что-то важное.

В большинстве случаев, я считаю, компилятор должен поступать правильно для вас. Например, malloc() ожидает size_t, и компилятор знает, от прототипа функции, что он делает, так что если вы пишете

int size_i_need = 10; 
char *buf = malloc(size_i_need); 

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

Если переменные, которые вы используете, уже unsigned, тем лучше!

Точно так же, если вы напишете

fgets(buf, sizeof(buf), ifp); 

компилятор снова вставить в соответствующее преобразование. Здесь, я думаю, я вижу, что вы получаете, 64-битный компилятор может выдать предупреждение о преобразовании down из long в int. Теперь, когда я думаю об этом, я не уверен, почему у меня не было этой проблемы, потому что это распространенная идиома.

(Вы также задан вопрос о переходе unsigned long к malloc, и на машине, где size_t меньше, чем long, я полагаю, что могли бы получить предупреждения, тоже. Это то, что вы были обеспокоены?)

Если вы у вас есть сокращение, которого вы не можете избежать, и ваш компилятор или какой-либо другой инструмент предупреждают об этом, и вы хотите избавиться от предупреждения safely, вы можете использовать бросок и утверждение. То есть, если вы пишете

unsigned long long size_i_need = 23; 
char *buf = malloc(size_i_need); 

это может получить предупреждение на машине, где size_t - 32 бит. Таким образом, вы можете отключить предупреждение с броском (в предположении, что ваши неподписанные длинные длинные значения никогда не будет на самом деле быть слишком большим), а затем создать резервную копию предположения с призывом assert:

unsigned long long size_i_need = 23; 
assert(size_i_need <= SIZE_MAX); 
char *buf = malloc((size_t)size_i_need); 

По моему опыту, самая большая неприятность печатает эти вещи. Если вы пишете

printf("int size = %d\n", sizeof(int)); 

или

printf("string length = %d\n", strlen("abc")); 

на 64-битной машине, современный компилятор обычно (и правильно) предупреждают, что «формат определяет тип„Int“, но аргумент имеет тип" unsigned long '", или что-то в этом роде. Вы можете исправить это двумя способами: бросить значение в соответствии с форматом PRINTF или изменить формат PRINTF, чтобы соответствовать значению:

printf("int size = %d\n", (int)sizeof(int)); 
printf("string length = %lu\n", strlen("abc")); 

В первом случае, вы в предположении, что результат sizeof «s будет соответствовать в int (что, вероятно, является безопасной ставкой). Во втором случае вы предполагаете, что size_t - это фактически unsigned long, что может быть истинным в 64-битном компиляторе, но может быть неверным для некоторых других. Так что на самом деле безопаснее использовать явное приведение во втором случае тоже:

printf("string length = %lu\n", (unsigned long)strlen("abc")); 

Суть заключается в том, что абстрактные типы как size_t не работают так хорошо с printf; это то, где мы можем видеть, что стиль вывода C++ cout << "string length = " << strlen("abc") << endl имеет свои преимущества.

Чтобы решить эту проблему, есть некоторые специальные printf модификаторы, которые гарантированно соответствуют size_t, и я думаю, что off_t и несколько других абстрактных типов, хотя они не так хорошо известны. (Я не был уверен, где их искать, но пока я сочиняю этот ответ, некоторые комментаторы уже напомнили мне.) Так что лучший способ распечатать одну из этих вещей (если вы помните, повторное использование старых компиляторов) будет

printf("string length = %zu\n", strlen("abc")); 

Итог:

  1. Вы, очевидно, не придется беспокоиться о прохождении простой int или простой unsigned к функции как calloc, ожидающей size_t.
  2. При вызове то, что может привести к подавленным, например, пропусканием size_t к fgets где size_t составляет 64 бита, но int 32, или проходя unsigned long long к calloc где size_t только 32 бита, вы можете получить предупреждение. Если вы не можете сделать переданные типы меньшими (что в общем случае вы не сможете сделать), у вас будет мало выбора, чтобы заставить замолчать предупреждения, но вставить бросок. В этом случае, чтобы быть строго правильным, вы можете добавить некоторые утверждения.

Со всем сказанным, я не уверен, что я действительно ответил на ваш вопрос, поэтому, если вы хотите получить дополнительные разъяснения, пожалуйста, спросите.

+0

Было бы лучше 'size_t size_i_need ...'. Зачем вам использовать 'int'? Заметьте, существует модификатор 'printf', поскольку C99:' "% zu" ': никаких проблем вообще. Для типов 'stdint.h' существует' inttypes.h', который обеспечивает правильные строки преобразования. – Olaf

+0

Вам не нужно бросать, чтобы избежать предупреждений. Существует '% zu' для печати' size_t' (C99). Хуже того, приведение к 'int' может привести к потере информации. (Я не уменьшал, кстати). –

+0

Я добираюсь туда ... –

1

Мой другой ответ получил waaaaaaay слишком долго, так что вот короткий.

  1. Заявить свои переменные из натуральных и подходящих типов. Пусть компилятор позаботится о большинстве конверсий. Если у вас есть что-то, что может быть или может быть размером, используйте size_t. (Аналогично, если у вас есть что-то, что связано с размерами файлов или смещениями, используйте off_t.)
  2. Старайтесь не смешивать подписанные и неподписанные типы.
  3. Если вы получаете предупреждения о возможной потере данных из-за того, что более крупные типы преобразуются вниз в возможно более мелкие типы, и если вы не можете изменить типы, чтобы предупреждения были удалены, сначала (a) убедитесь, что значения, на практике, никогда не будет переполняться меньшим типом, тогда (б) добавьте явное преобразование с понижением преобразований, чтобы предупредить предупреждение, и за дополнительный кредит (c) добавьте утверждение в документ и обеспечите соблюдение вашего предположения:

.

assert(size_i_need <= SIZE_MAX); 
char *buf = malloc((size_t)size_i_need); 
3

Я немного запутался, как использовать size_t, когда другие типы данных, как междунар, неподписанных долгое INT и без знака долго долго ИНТ присутствуют в программе .

Не следует игнорировать предупреждения. Предупреждения направлены на то, чтобы обратить ваше внимание на области вашего кода, которые могут быть проблематичными. Гораздо лучше занять несколько минут, чтобы понять, что предупреждает вам предупреждение - и исправить это, а затем немного поучаствовать в этом, когда вы нажмете на угловой футляр и споткнуться в неопределенное поведение.

size_t сам по себе является типом данных, как и любой другой. Хотя он может меняться, он, как правило, представляет собой не что иное, как unsigned int, охватывающий диапазон положительных значений, который может быть представлен int, включая 0 (размер типа должен был быть согласованным между платформами, фактические байты на каждом из них могут отличаться). Ваш выбор типа данных является базовой и фундаментальной частью программирования. Вы выбираете тип, основанный на диапазоне значений, которые ваша переменная может представлять (или должна ограничиваться представлением). Так что, если вы имеете дело с , не может быть отрицательным, то unsigned или size_t является правильным выбором. Затем этот выбор позволяет компилятору помочь определить области, где ваш код может привести к нарушению.

При компиляции с включенными предупреждениями (например, -Wall -Wextra), которые вы должны использовать на каждом компиляторе, вы будете предупреждены о возможных конфликтах при использовании типа данных. (т.е. сравнение между значениями signed и unsigned и т. д. ...) Это важный!

Практически все современные x86 & x86_64 компьютеры используют двойки-комплимент представления для знаковых значений. Проще говоря, это означает, что если самый левый бит подписанного числа равен 1, то значение равно отрицательным. Здесь лежат тонкие ловушки, которые вы можете упасть при смешивании/отливке или сравнении чисел различного типа. Если вы решите нанести номер unsigned на номер signed, и этот номер имеет самый значительный бит, заполненный, ваше большое количество просто стало очень маленьким числом.

Что делать в этих двух случаях? В ролях, или просто игнорировать возможные предупреждения ...

Вы делаете то, что вы делаете каждый раз, когда вы столкнулись с предупреждениями от компилятора. Вы анализируете, что вызывает предупреждение, а затем вы его исправляете (или если вы не можете его исправить) (т. Е. Происходит из какой-либо библиотеки, к которой у вас нет доступа) - вы достаточно хорошо понимаете предупреждение, что можете принять обоснованное решение игнорировать его зная, что вы не попали любые угловые случаи, которые могли бы привести к непредсказуемому поведению

в ваших примерах (в то время как ни следует производить предупреждение, они могут на некоторых компиляторах):.

arr = calloc (mrow, sizeof(long int *)); 

Что такое диапазон sizeof(long int *)? Ну, это диапазон размеров стрелки. Итак, что это? (4 bytes на x86 или 8 bytes на x86_64). Таким образом, диапазон значений 4-8, да что может быть надежно закреплен с броском на size_t, если это необходимо, или лучше просто:

arr = calloc (mrow, sizeof *arr); 

Глядя на следующем примере:

char save[22]; 
... 
fgets(save, sizeof(save), stdin) 

Здесь снова, что возможный диапазон sizeof save? От 22 - 22. Так что да, если выдается предупреждение о том, что sizeof возвращает long unsigned и fgets, звонки на int, 22 могут быть отлиты до int.

+1

Это «* это вообще не что иное, как« unsigned int' * »вводит в заблуждение и лучше читать« * ... «неподписанное» целое число », как, например, в Linux x86_64' size_t' является 'unsigned long'. – alk

+0

У вас там хороший момент. Благодарю. Исправлена. –

+0

x86_64 был всего лишь примером, это может быть что-то другое на другой платформе. Вот почему 'size_t' был изобретен. – alk

1

Когда бросать size_t

Вы не должны.

Используйте его там, где это уместно.

  • (Как вы уже заметили) функции библиотеки libc сообщают вам, где это происходит.
  • Дополнительно используйте его для индексирования массивов.

Если сомневаются тип удовлетворяет потребности вашей программы вы могли бы пойти на полезное заявление утверждения в соответствии с Steve Summit «s answer и если он не начать с дизайном вашей программы.


Подробнее об этом здесь Дэн Сакс: "Why size_t matters" и "Further insights into size_t"

+0

(Это дополнительный вопрос, но не стоит отдельного сообщения.) Что касается примера' fgets(): на основе статьи (Great reads, thanks!) и опция sloppy casting от 'size_t' до' int', кажется, что 'fgets()' и 'size_t' не сочетаются друг с другом. Поэтому лучший вариант, похоже, использует' define 'для размера буфера' fgets()? –

+1

@brauner: 'fgets()' старше, чем 'size_t'. Нам нужно жить с этим. Если размер, который будет передан' fgets() ', может отличаться, тогда чтобы остаться на стороне сохранения, добавьте код, чтобы проверить, будет ли то, что вы передадите как 'size', переполнило бы' int'. Если можно использовать константу, то проще. :-) – alk

Смежные вопросы