Важность объявления переменной без знака - PullRequest
11 голосов
/ 16 сентября 2010

Важно ли объявлять переменную без знака, если вы знаете, что она никогда не должна быть отрицательной? Помогает ли это предотвратить что-либо, кроме отрицательных чисел, вводимых в функцию, которая не должна иметь их?

Ответы [ 14 ]

21 голосов
/ 16 сентября 2010

Объявление переменных для семантически неотрицательных значений как unsigned - это хороший стиль и хорошая практика программирования.

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

Стоит также отметить, что работа с целыми числами без знака требует знания некоторых специальных методов без знака.Например, «классическим» примером, который часто упоминается в связи с этой проблемой, является обратная итерация

for (int i = 99; i >= 0; --i) {
  /* whatever */
}

Вышеуказанный цикл выглядит естественно со знаком i, но его нельзя напрямую преобразовать в беззнаковую форму,это означает, что

for (unsigned i = 99; i >= 0; --i) {
  /* whatever */
}

на самом деле не делает то, что задумано (на самом деле это бесконечный цикл).Надлежащая техника в этом случае - либо

for (unsigned i = 100; i > 0; ) {
  --i;
  /* whatever */
}

, либо

for (unsigned i = 100; i-- > 0; ) {
  /* whatever */
}

. Это часто используется в качестве аргумента против неподписанных типов, то есть якобы вышеупомянутые неподписанные версии цикла выглядят «неестественно».и "нечитаемый".В действительности, хотя проблема, с которой мы здесь имеем дело, это общая проблема работы около левого конца закрытого открытого диапазона.Эта проблема проявляется по-разному в C и C ++ (например, обратная итерация по массиву с использованием техники «скользящего указателя» обратной итерации по стандартному контейнеру с использованием итератора).То есть, независимо от того, насколько неприглядными могут выглядеть вышеприведенные циклы без знака, невозможно полностью их избежать, даже если вы никогда не используете целочисленные типы без знака.Таким образом, лучше изучить эти методы и включить их в свой набор установленных идиом.

6 голосов
/ 16 сентября 2010

Это не мешает людям злоупотреблять вашим интерфейсом, но, по крайней мере, они должны получить предупреждение, если они не добавят приведение в стиле C или static_cast, чтобы заставить его уйти (в этом случае вы не сможете помочь им дальше).

Да, в этом есть значение, поскольку оно правильно выражает семантику, которую вы хотите.

4 голосов
/ 16 сентября 2010

Важно ли объявлять переменную без знака, если вы знаете, что она никогда не должна быть отрицательной?

Конечно, это не важно. Некоторые люди (Страуструп и Скотт Мейерс, см. «Беззнаковые против подписанных - ошибочна ли Бьярне?» ) отвергают идею о том, что переменная должна быть без знака только потому, что она представляет количество без знака. Если смысл использования unsigned будет означать, что переменная может хранить только неотрицательные значения, вам нужно как-то проверить это. В противном случае все, что вы получите, это

  • Тип, который скрытно скрывает ошибки, потому что не позволяет выставлять отрицательные значения
  • Удвоение положительного диапазона соответствующего типа со знаком
  • Определенная семантика переполнения / битового сдвига / и т. Д.

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

assert((idx >= 0) && "Index must be greater/equal than 0!");

Тип unsigned также вносит много ошибок. Вы должны быть осторожны, когда используете его в вычислениях, которые могут быть временно меньше нуля (цикл обратного отсчета или что-то в этом роде) и особенно в автоматических повышениях, которые происходят в языках C и C ++ среди значений без знака и со знаком

// assume idx is unsigned. What if idx is 0 !?
if(idx - 1 > 3) /* do something */;
4 голосов
/ 16 сентября 2010

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

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

struct T {
    int x;
    int y;
    unsigned int width;
    unsigned int height;
};

Идея в том, что невозможно иметь отрицательную ширину.Хорошо, какой тип данных вы используете для хранения правого края прямоугольника?

int right = r.x + r.width; // causes a warning on some compilers with certain flags

и, конечно, он по-прежнему не защищает вас от целочисленных переполнений.Таким образом, в этом сценарии , хотя width и height концептуально не могут быть отрицательными, в действительности их увеличение unsigned не дает никакой выгоды, за исключением того, что некоторые броски избавляются от предупреждений относительно микширования со знакоми неподписанные типы.В конце концов, по крайней мере, для таких случаев лучше просто сделать их все int с, в конце концов, скорее всего, у вас недостаточно широкого окна, чтобы нужно , чтобы оно было unsigned.

4 голосов
/ 16 сентября 2010

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

int idx = [...];
if ((idx >= 0)&&(idx < arrayLength)) printf("array value is %i\n", array[idx]);

Вы можете просто написать:

unsigned int idx = [...];
if (idx < arrayLength) printf("array value is %i\n", array[idx]);
4 голосов
/ 16 сентября 2010

Это делает две вещи:

1) Это дает вам удвоенный диапазон значений ваших значений без знака. Когда «знаковый» старший бит используется в качестве знакового бита (1 означает отрицательный, 0 для положительного), когда «без знака» вы можете использовать этот бит для данных. Например, тип char изменяется от -128 до 127, тип unsigned char идет от 0 до 255

.

2) Это влияет на то, как действует оператор >>, особенно при смещении отрицательных значений вправо.

2 голосов
/ 16 сентября 2010

Это имеет значение по той же причине, что и "const правильность". Если вы знаете, что конкретное значение не должно изменяться, объявите его const и позвольте компилятору помочь вам. Если вы знаете, что переменная всегда должна быть неотрицательной, объявите ее как unsigned, и компилятор поможет вам выявить несоответствия.

(То есть, и вы можете выразить числа вдвое больше, если в этом контексте используете unsigned int вместо int.)

1 голос
/ 17 сентября 2010

Есть две основные вещи, которые unsigned дают вам

  • позволяет безопасно использовать оператор смещения вправо >> для любого значения, поскольку оно не может быть отрицательным - использование смещения вправо для отрицательного значения не определено.

  • это дает вам модификацию 2 ^ n арифметики. Со значениями со знаком эффект недополнения / переполнения не определен. Со значениями без знака вы получаете mod 2 ^ n арифметики, поэтому 0U - 1U всегда даст вам максимально возможное значение без знака (которое всегда будет на 1 меньше, чем степень 2).

1 голос
/ 16 сентября 2010

Смешивание типов со знаком и без знака может быть большой головной болью. Результирующий код часто будет раздутым, неправильным или и тем, и другим (*). Во многих случаях, если вам не нужно хранить значения между 2 147 483 648 и 4 294 967 295 в 32-разрядной переменной или вам не нужно работать со значениями, превышающими 9 223 372 036 854 775 807, я бы рекомендовал вообще не беспокоиться о типах без знака.

(*) Что должно произойти, например, если программист делает:

{ Question would be applicable to C, Pascal, Basic, or any other language }
  If SignedVar + UnsignedVar > OtherSignedVar Then DoSomething;

Я полагаю, что старый Паскаль Борланда справился бы с вышеописанным сценарием, преобразовав SignedVar и UnsignedVar в больший тип со знаком (между прочим, самый большой поддерживаемый тип, кстати, был подписан, поэтому каждый неподписанный тип можно преобразовать в больший со знаком). Это приведет к большому коду, но это будет правильно. В C, если одна подписанная переменная отрицательна, результат, вероятно, будет численно неверным, даже если UnsignedVar содержит ноль. Существует также много других плохих сценариев.

1 голос
/ 16 сентября 2010

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

Я бы не использовал тип без знака, если бы знал, что переменная не должна быть отрицательной, а скорее, если она не можетт бытьНапример, size_t - это тип без знака, поскольку тип данных просто не может иметь отрицательный размер.Если значение может быть отрицательным, но не должно быть, проще выразить это, имея тип со знаком и используя что-то вроде i < 0 или i >= 0 (эти условия имеют вид false и true соответственноесли i является типом без знака, независимо от его значения).

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...