Объясняя этот отрывок в "О size_t и ptrdiff_t"
В этом блоге запись Андрея Карпова под названием "О size_t
а также ptrdiff_t
" он завершает с
Как видит читатель, использование типов ptrdiff_t и size_t дает некоторые преимущества для 64-битных программ. Однако это не универсальное решение для замены всех неподписанных типов на size_t. Во-первых, это не гарантирует корректную работу программы в 64-битной системе. Во-вторых, наиболее вероятно, что из-за этой замены появятся новые ошибки, будет нарушена совместимость форматов данных и так далее. Не следует забывать, что после этой замены объем памяти, необходимый для программы, также значительно увеличится. Увеличение необходимого объема памяти замедлит работу приложения, поскольку в кеше будет храниться меньше объектов, с которыми приходится иметь дело.
Я не понимаю эти требования, и я не вижу их в статье,
"Скорее всего, из-за этой замены появятся новые ошибки, будет нарушена совместимость форматов данных и т. д."
Как это вероятно, как не может быть ошибки до того, как миграция и перенос типа приведут к ошибке? Не понятно когда типы (size_t
а также ptrdiff_t
) кажется более ограничительным, чем то, что они заменяют.
Не следует забывать, что после этой замены объем памяти, необходимый для программы, также значительно увеличится.
Мне неясно, как или почему необходимый объем памяти "сильно" увеличится или увеличится вообще? Я понимаю, что если это так, то выводы Андрея следуют.
3 ответа
Статья содержит очень сомнительные претензии.
Прежде всего, size_t
это тип, возвращаемый sizeof
, uintptr_t
целочисленный тип, который может хранить любой указатель на void
,
В статье утверждается, что size_t
а также uintptr_t
являются синонимами. Они не. Например, на сегментированной MSDOS с большими моделями памяти максимальное количество элементов в массиве могло бы поместиться в size_t
16 бит, но указатель требует 32 бита. Теперь они являются синонимами наших распространенных моделей плоской памяти Windows, Linux.
Еще хуже утверждение о том, что вы можете хранить указатель в ptrdiff_t
или что это будет синонимом intptr_t
:
Размер
size_t
а такжеptrdiff_t
всегда совпадают с размером указателя. Из-за этого именно эти типы должны использоваться в качестве индексов для больших массивов, для хранения указателей и арифметики указателей.
Это совсем не так. ptrdiff_t
является типом значения вычитания указателя, но вычитание указателя определяется только тогда, когда оба указателя указывают на один и тот же объект или сразу после него, а не только где-нибудь в памяти.
С другой стороны ptrdiff_t
может быть выбран, чтобы быть больше, чем size_t
- это потому, что если у вас есть массив размером больше, чем MAX_SIZE / 2
элементы, вычитающие указатель на первый элемент из указателя на последний элемент или чуть дальше, будут иметь неопределенное поведение, если ptrdiff_t
такой же ширины, как size_t
, Inded, стандарт говорит, что size_t
может быть только 16 бит в ширину, но ptrdiff_t
должно быть не менее 17 ] ( http://port70.net/~nsz/c/c11/n1570.html).
В линуксе ptrdiff_t
а также size_t
имеют одинаковый размер - и в 32-битном Linux можно выделить объект размером более PTRDIFF_MAX
элементы. И, как было отмечено в комментариях, стандарт не требует ptrdiff_t
быть даже того же ранга, что и size_t
хотя такая реализация была бы чистым злом.
Если нужно следовать совету и использовать size_t
а также ptrdiff_t
хранить указатели, конечно, нельзя идти правильно.
Что касается заявления о том, что
Не следует забывать, что после этой замены объем памяти, необходимый для программы, также значительно увеличится.
Я бы оспорил это утверждение - увеличение требований к памяти будет довольно скромным по сравнению с уже существующим увеличением потребления из общего 64-разрядного выравнивания, выравнивания стека и 64-разрядных указателей, которые присущи переходу в 64-разрядную среду,
Что касается заявления о том, что
"Скорее всего, из-за этой замены появятся новые ошибки, будет нарушена совместимость форматов данных и т. д."
Это, конечно, верно, но, скорее всего, если вы кодируете такой глючный код, вы случайно "исправите" старые ошибки в процессе, например signed/unsigned int
пример:
int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B); //Error
printf("%i\n", *ptr);
где и исходный, и новый код будут иметь неопределенное поведение (доступ к элементам массива вне границ), но новый код будет казаться "правильным" и на 64-битных платформах.
Ну, любое изменение может привести к ошибкам. В частности, я могу себе представить, что изменение размеров может сломаться там, где были применены менее строгие в отношении типов (например, если предположить, что целые или длинные значения совпадают с указателями там, где их нет). Любая двоичная структура, записанная в файл, не будет доступна для чтения напрямую, и любой RPC может потерпеть неудачу, в зависимости от протоколов.
Требования к памяти, очевидно, увеличатся, так как размер большинства объектов в памяти увеличится. Большая часть данных будет выровнена по 64-битным границам, что означает больше "дыр". Использование стека увеличится, что может привести к более частым ошибкам в кеше.
В то время как все обобщения могут быть истинными или ложными, единственный способ выяснить это - провести некоторый надлежащий анализ имеющейся системы.
В качестве общего предложения, используя size_t
а также ptrdiff_t
гораздо предпочтительнее, чем, скажем, простой unsigned int
а также int
, size_t
а также ptrdiff_t
в значительной степени единственный способ написания надежной и широко переносимой программы.
Тем не менее, нет такого понятия, как бесплатный обед. Правильно используя size_t
тоже требует некоторой работы - просто если вы знаете, что делаете, это займет меньше работы, чем попытка достичь того же результата без использования size_t
,
Также, size_t
есть проблема, которую вы не можете распечатать, используя %d
или же %u
, В идеале вы хотите использовать %zu
Но, к сожалению, не все реализации поддерживают это.
Если у вас есть большая и плохо написанная программа, которая не использует size_t
Это, вероятно, полно ошибок. Некоторые из этих ошибок будут замаскированы или обойдены. Если вы попытаетесь изменить его, чтобы использовать size_t
, определенное количество обходных путей программы не удастся, возможно, обнаружив некогда скрытые ошибки. В конце концов вы решите их и получите более надежную и более надежную и более переносимую программу, которую вы хотите, но процесс будет непростым. Я подозреваю, что именно это подразумевает автор, "скорее всего, из-за этой замены появятся новые ошибки".
Изменение программы для использования size_t
вроде как пытается добавить const
во всех нужных местах. Вы вносите изменения, которые, по вашему мнению, необходимо внести, и перекомпилируете, и вы получаете кучу ошибок и предупреждений, и исправляете их, и перекомпилируете, и получаете кучу дополнительных ошибок и предупреждений и т. Д. Это по меньшей мере неприятно, и иногда тонна работы. Но, как правило, это единственный путь, если вы хотите сделать код более надежным и переносимым.
Большая часть проблемы заключается в поддержании компилятора. Он будет предупреждать о множестве вещей, и вы, как правило, захотите исправить все, начто он жалуется, даже если некоторые из них жалуются на галочку и вряд ли вызовут проблему. Но опасно говорить: "Да, я могу проигнорировать это конкретное предупреждение", поэтому, в конце концов, как я уже сказал, вы, как правило, захотите все исправить.
Наиболее привлекательным утверждением автора является
объем памяти, необходимый для программы, также значительно увеличится.
Я подозреваю, что это преувеличение - в большинстве случаев я сомневаюсь, что память "значительно" увеличится, но, вероятно, увеличится, по крайней мере, немного. Проблема в том, что в 64-битной системе size_t
а также ptrdiff_t
скорее всего, будут 64-битные типы. Если по какой-либо причине у вас есть большие массивы из них, или большие массивы структур, содержащих их, и если вы использовали какой-то 32-битный тип (возможно, простойint
или жеunsigned int
) раньше, да, вы увидите увеличение памяти.
И тогда вы захотите спросить: мне действительно нужно уметь описывать 64-битные размеры? 64-битное программирование дает вам две вещи: (а) возможность адресовать более 4 ГБ памяти и (б) возможность иметь один объект размером более 4 ГБ. Если вы хотите, чтобы общее использование данных превышало 4 ГБ, но вам никогда не нужно иметь один объект размером более 4 ГБ, и если вы никогда не хотите читать более 4 ГБ данных за раз из файла (используя не замужем read
или жеfread
то есть) вам не нужны везде 64-битные переменные размера.
Таким образом, чтобы избежать раздувания, вы можете сделать осознанный выбор, скажем, unsigned int
(или даже unsigned short
) вместо size_t
в некоторых местах. В качестве тривиального примера, если у вас было
size_t x = sizeof(int);
printf("%zu\n", x);
Вы можете изменить это на
unsigned int x = sizeof(int);
printf("%u\n", x);
без потери переносимости, потому что я могу с полной уверенностью гарантировать, что ваш код никогда не будет работать на компьютере с 34359738368-разрядным int
с (или, по крайней мере, не в наших жизнях:-)).
Но последний пример, хотя и тривиальный, также иллюстрирует другие проблемы, которые имеют тенденцию вмешиваться. Аналогичный код
unsigned int x = sizeof(y);
printf("%u\n", x);
не так уж очевидно безопасно, потому что все y
есть вероятность, что он может быть настолько большим, что его размер не помещается в беззнаковое целое. Так что, если вы или ваш компилятор действительно заботитесь о корректности типа, при назначении могут быть предупреждения о возможной потере данных size_t
в unsigned int
, И чтобы отключить эти предупреждения, вам может потребоваться явное приведение, как в
unsigned int x = (unsigned int)sizeof(int);
И этот состав, возможно, совершенно уместен. Компилятор работает при условии, что любой объект может быть очень большим, что любая попытка заклинить size_t
в unsigned int
может потерять данные. Актеры говорят, что вы подумали об этом случае: вы говорите: "Да, я знаю это, но в этом случае я знаю, что это не переполнится, поэтому, пожалуйста, не предупреждайте меня об этом больше, но пожалуйста, предупредите меня о любых других, которые могут быть не так безопасны ".
PS Меня опускают, поэтому в случае, если у меня сложилось неправильное впечатление, позвольте мне прояснить это (как я сказал в своем первом абзаце) size_t
а также ptrdiff_t
значительно предпочтительнее В общем, есть все основания их использовать, нет веских причин не использовать их. (Приходите к этому, Карпов тоже не говорил, чтобы не использовать их - просто выделил некоторые проблемы, которые могут возникнуть на этом пути.)