Вопрос по c, unsigned, string, debugging – «Strlen (s1) - strlen (s2)» никогда не меньше нуля

76

В настоящее время я пишу программу на C, которая требует частого сравнения длин строк, поэтому я написал следующую вспомогательную функцию:

<code>int strlonger(char *s1, char *s2) {
    return strlen(s1) - strlen(s2) > 0;
}
</code>

Я заметил, что функция возвращает истину, даже когдаs1 имеет более короткую длину, чемs2, Может кто-нибудь объяснить это странное поведение?

Это был несчастный случай, я не знал, что такое щедрость, лол. -_- Вид неловко ... Adrian Monk
Это способ написания на Fortran-66.return strlen(s1) > strlen(s2);. Jonathan Leffler
Полагаю, это хорошо для Алекса Локвуда, потому что его отличный ответ привлечет больше внимания ... так что всеup-vote Alex Lockwood's answer!! : D Adrian Monk
Я думаю, что @TimThomas лучше держать награду открытой до последней допустимой даты, чтобы его вопрос тоже привлек к себе внимание. Он неосознанно потерял свои 100 очков репутации, пусть получит немного назад ... Krishnabhadra
@TimThomas: Почему вы предлагаете награду за этот вопрос? Вы говорите, что этому не уделяется достаточно внимания, но, похоже, вы вполне довольныAlex Lockwood's answer, Не уверен, что еще нужно, чтобы выиграть награду! :) eggyal

Ваш Ответ

3   ответа
1

ответ лучшее решение (компактная, четкая семантика и т. д.).

Иногда имеет смысл явно преобразовать в подписанную формуsize_t: ptrdiff_tнапример,

return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;

Если вы сделаете это, вы хотите быть уверены, чтоsize_t значение вписывается вptrdiff_t (в котором меньше битов мантиссы).

174

с чем вы столкнулись, - это своеобразное поведение, которое возникает в C при обработке выражений, содержащих как подписанные, так и неподписанные количества.

Когда выполняется операция, когда один операнд подписан, а другой - без знака, C будет неявно преобразовывать подписанный аргумент в unsigned и выполнять операции, предполагая, что числа неотрицательны. Это соглашение часто приводит к неинтуитивному поведению для реляционных операторов, таких как< а также>.

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

return strlen(s1) - strlen(s2) > 0;

возвращается1 даже еслиs1 короче чемs2, Чтобы исправить вашу функцию, используйте этот код:

return strlen(s1) > strlen(s2);

Добро пожаловать в чудесный мир C! :)

Additional Examples

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

Важная концепция для понимания при работе с беззнаковыми / подписанными переменными в C заключается в том, чтоif there is a mix of unsigned and signed quantities in a single expression, signed values are implicitly cast to unsigned.

Example #1:

Рассмотрим следующее выражение:

-1 < 0U

Так как второй операнд не подписан, первыйimplicitly cast без знака, и, следовательно, выражение эквивалентно сравнению,

4294967295U < 0U

что, конечно, неверно. Вероятно, это не то поведение, которого вы ожидали.

Example #2:

Рассмотрим следующий код, который пытается суммировать элементы массиваaгде количество элементов задается параметромlength:

int sum_array_elements(int a[], unsigned length) {
    int i;
    int result = 0;

    for (i = 0; i <= length-1; i++) 
        result += a[i];

    return result;
}

Эта функция предназначена для демонстрации того, насколько легко могут возникать ошибки из-за неявного преобразования типов со знака в без знака. Кажется вполне естественным передать параметрlength как без знака; в конце концов, кто бы хотел использовать отрицательную длину? Критерий остановкиi <= length-1 также кажется довольно интуитивным. Однако при запуске с аргументомlength равно0Сочетание этих двух дает неожиданный результат.

С параметромlength без знака, вычисление0-1 выполняется с использованием арифметики без знака, что эквивалентно модульному сложению. Результат тогдаUMax,<= сравнение также выполняется с использованием сравнения без знака, и поскольку любое число меньше или равноUMaxСравнение всегда имеет место. Таким образом, код будет пытаться получить доступ к недопустимым элементам массиваa.

Код можно исправить либо объявивlength бытьintили путем изменения тестаfor цикл должен бытьi < length.

Conclusion: When Should You Use Unsigned?

Я надеваю & APOS; т хочет заявить что-либо слишком спорным здесь, но вот некоторые из правил, которые я часто придерживаться, когда я пишу программы на C.

DON'T use just because a number is nonnegative. It is easy to make mistakes, and these mistakes are sometimes incredibly subtle (as illustrated in Example #2).

DO use when performing modular arithmetic.

DO use when using bits to represent sets. This is often convenient because it allows you to perform logical right shifts without sign extension.

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

Еще один прекрасный пример, как писатьless делает программуmore правильный.
@TimThomas Он должен приводить так или иначе, а приведение без знака к подписи приведет к потере информации, то есть к половине пространства значений.
Это неcast, Этоconverts, Семестрcast относится только к явному оператору приведения, состоящему из заключенного в скобки имени типа. Оператор приведения явно указывает преобразование; преобразование может быть явным или неявным.
Строго говоря, вычитание выполняется между двумяsize_t Значения, которые гарантированы без знака и без знака арифметической обертки по модулю соответствующей степени двух. Единственное место, где возможно преобразование со знаком / без знака, находится вresult > 0 часть, гдеresult этоsize_t значение от вычитания двух размеров.
Я нахожу отрицательные целые числа достаточно редкими в своем коде, поэтому я использую противоположный подход и используюunsigned int если нет какой-либо конкретной причины не делать этого. Преимущество этого заключается в том, что все операции четко определены (даже «обтекание»), хотя по общему признанию это может потребовать осторожности при работе с некоторыми неравенствами.
25

strlen возвращаетsize_t который являетсяtypedef дляunsigned тип.

Так,

(unsigned) 4 - (unsigned) 7 == (unsigned) - 3

Всеunsigned значения больше или равны0, Попробуйте преобразовать переменные, возвращаемыеstrlen вlong int.

ptrdiff_t - правильное переносимое приведение. Обычно long int представляет собой 32-разрядное целое число со знаком в 64-разрядных системах (в 64-разрядных системах это 64-разрядные указатели). На самом деле, и Visual C ++, и gcc для x86 и x86_64 используют 32-битные long.
я думалptrdiff_t был для вычитания указателей, а не вычитанияsize_t ценности...
Не существует типа POSIX для "вычитания"size_t Значения & Quot ;; С определяет это простоsize_t поскольку это целочисленный тип, и типы совпадают. Вы можете утверждать, что этоoff_t, но это фактически для смещений файлов. Таким образом, лучшее, что вы сделаете, - этоsize_t требуется для хранения любого индекса, который может обрабатывать платформа, тогда он также может представлять любое значение указателя, поскольку он может использоваться для индексации байтов из0, таким образомptrdiff_t должно быть такое же количество бит, какsize_tделая это простоsigned версияsize_t.

Похожие вопросы