При использовании целочисленных значений в моем собственном коде я всегда стараюсь учитывать подписанность, спрашивая себя, должно ли целое число быть подписанным или беззнаковым.
Когда я уверен, что значение никогда не будет отрицательным, я использую целое число без знака.
И я должен сказать, что это происходит в большинстве случаев.
Тщательно продумывать, какой тип наиболее подходит каждый раз, когда вы объявляете переменную, очень хорошая практика! Это означает, что вы осторожны и профессиональны. Вам следует учитывать не только подпись, но и потенциальное максимальное значение, которое вы ожидаете получить от этого типа.
Причина, по которой вы не должны использовать подписанные типы, когда они не нужны, не имеет ничего общего с производительностью, а с безопасностью типов. Есть много потенциальных, тонких ошибок, которые могут быть вызваны подписанными типами:
Различные формы неявного продвижения, существующие в C, могут привести к тому, что ваш тип изменит подпись неожиданным и, возможно, опасным образом. Правило целочисленного продвижения , являющееся частью обычных арифметических преобразований , преобразование lvalue при назначении, продвижения по умолчанию для аргумента , используемого для пример списков ВА и тд.
При использовании любой формы побитовых операторов или аналогичного аппаратного программирования типы со знаком опасны и могут легко вызывать различные формы неопределенного поведения.
Объявляя свои целые числа без знака, вы автоматически пропускаете множество вышеупомянутых опасностей. Точно так же, объявив их размером unsigned int
или больше, вы избавитесь от множества опасностей, связанных с целочисленными повышениями.
И размер, и подпись важны, когда речь идет о написании надежного, переносимого и безопасного кода. По этой причине вы всегда должны использовать типы из stdint.h
, а не нативные, так называемые «примитивные типы данных» языка C.
Поэтому я спросил себя: «есть ли для этого веские причины, или люди просто используют целые числа со знаком, потому что им все равно»?
Я действительно не думаю, что это потому, что им все равно, или потому что они ленивы, хотя объявление всего int
иногда называют «небрежной печатью» - что означает небрежно выбранный тип больше, чем означает слишком ленив, чтобы напечатать.
Я скорее верю, что это потому, что им не хватает более глубокого знания различных вещей, которые я упомянул выше. Есть пугающее количество опытных программистов на C, которые не знают, как неявные продвижения типов работают в C, и как подписанные типы могут вызывать плохо определенное поведение при использовании вместе с определенными операторами.
Это на самом деле очень частый источник тонких ошибок. Многие программисты смотрят на предупреждение компилятора или на специфическую ошибку, которую они могут устранить, добавив приведение. Но они не понимают почему, они просто добавляют актерский состав и идут дальше.
для (без знака int i = foo.Length () - 1; i> = 0; --i) {}
Для меня это просто плохой дизайн
Действительно, это так.
Когда-то циклы обратного отсчета давали бы более эффективный код, потому что выбор компилятора добавляет инструкцию «ветвь, если ноль» вместо инструкции «ветвь, если больше / меньше / равен» - первая быстрее. Но это было в то время, когда компиляторы были действительно тупыми, и я не верю, что такие микрооптимизации более актуальны.
Так что редко когда есть причина иметь цикл обратного отсчета. Кто бы ни выступил с аргументом, вероятно, просто не мог думать нестандартно. Пример можно было переписать так:
for(unsigned int i=0; i<foo.Length(); i++)
{
unsigned int index = foo.Length() - i - 1;
thing[index] = something;
}
Этот код не должен влиять на производительность, но сам цикл стал намного проще для чтения, в то же время исправляя ошибку, которая была в вашем примере.
Что касается производительности в настоящее время, то, вероятно, следует потратить время на размышления о том, какая форма доступа к данным является наиболее идеальной с точки зрения использования кэша данных, а не чем-либо еще.
Некоторые люди могут также сказать, что целые числа со знаком могут быть полезны, даже для неотрицательных значений, для предоставления флага ошибки, обычно -1.
Это плохой аргумент. Хороший API-интерфейс использует специальный тип ошибок для отчетов об ошибках, например, перечисление.
Вместо использования какого-либо API-интерфейса уровня хобби, например
int do_stuff (int a, int b); // returns -1 if a or b were invalid, otherwise the result
у вас должно быть что-то вроде:
err_t do_stuff (int32_t a, int32_t b, int32_t* result);
// returns ERR_A is a is invalid, ERR_B if b is invalid, ERR_XXX if... and so on
// the result is stored in [result], which is allocated by the caller
// upon errors the contents of [result] remain untouched
Затем API последовательно зарезервирует возврат каждой функции для этого типа ошибки.
(И да, многие стандартные библиотечные функции злоупотребляют возвращаемыми типами для обработки ошибок. Это связано с тем, что в нем содержится множество древних функций за время до изобретения хорошей практики программирования, и они были сохранены так же, как и в обратном направлении. - из-за несовместимости. Поэтому, если вы найдете плохо написанную функцию в стандартной библиотеке, вам не следует разбегаться, чтобы написать столь же плохую функцию самостоятельно.)
В целом, звучит так, будто ты знаешь, что делаешь и думаешь о подписи. Это, вероятно, означает, что по знаниям вы уже опередили людей, которые написали эти посты и руководства, на которые вы ссылаетесь.
Руководство по стилю Google, например, сомнительно. Подобное можно сказать и о многих других таких стандартах кодирования, которые используют «доказательство властью». Просто потому, что в нем написано Google, NASA или ядро Linux, люди слепо проглатывают их, независимо от качества реального содержимого. В этих стандартах есть что-то хорошее, но они также содержат субъективные мнения, предположения или вопиющие ошибки.
Вместо этого я бы порекомендовал обратиться к настоящим профессиональным стандартам кодирования, таким как MISRA-C . Он заставляет много думать и заботиться о таких вещах, как подпись, продвижение шрифта и размер шрифта, когда менее подробные / менее серьезные документы просто пропускают его.
Существует также CERT C , который не такой подробный и тщательный, как MISRA, но, по крайней мере, качественный, профессиональный документ (и в большей степени ориентированный на разработку для настольных компьютеров / хостинг).