Отличие BSD memcmp(3) между руководством и реализацией

Согласно man memcmp на OSX Darwin:

Функция memcmp() возвращает ноль, если две строки идентичны, в противном случае возвращает разницу между первыми двумя отличающимися байтами (обрабатываются как беззнаковые значения char, так что, например, \200 больше, чем \0). Строки нулевой длины всегда идентичны. Это поведение не требуется для C, и переносимый код должен зависеть только от знака возвращаемого значения.

Однако, когда я тестирую это:

#include <stdio.h>
#include <string.h>

int main()
{
    printf("%i\n", memcmp("\200", "\0", 1));
    return (0);
}

Он отображает -1, что означает, что \200 меньше \0.

Есть ли какое-то объяснение этому?

Версия компилятора в соответствии с gcc --version — «Apple LLVM версии 9.0.0 (clang-900.0.39.2)», а система работает под управлением High Sierra 10.13.4.


person 1ShotSniper    schedule 20.05.2018    source источник
comment
В инструкции ошибка, там написано strcmp(), а не memcmp().   -  person Barmar    schedule 20.05.2018
comment
Код может вызвать неопределенное поведение, поскольку прототип для memcmp() может отсутствовать.   -  person alk    schedule 20.05.2018
comment
@Barmar Это имеет смысл, поскольку выполнение того же теста на strncmp() возвращает 128   -  person 1ShotSniper    schedule 20.05.2018
comment
Вам нужно включить <string.h>, чтобы получить прототип memcmp().   -  person Barmar    schedule 20.05.2018
comment
Возможный, но для меня не поддающийся проверке ответ: Darwin частично происходит от FreeBSD, FreeBSD раньше имела ошибку в своей memcmp реализации (blog.bramp.net/post/2009/08/10/), и хотя FreeBSD с тех пор исправила эту ошибку, Darwin так и не скопировал это исправление. Если кто-то может подтвердить, что это проблема, опубликуйте ее как ответ.   -  person    schedule 20.05.2018
comment
@Barmar @alk, по словам мужчины, он находится в libc. Однако я повторно протестировал только ‹string.h› с теми же результатами.   -  person 1ShotSniper    schedule 20.05.2018
comment
Тестирование @hvd с bcmp() возвращает ожидаемое 128, поэтому это не похоже на ту же ошибку.   -  person 1ShotSniper    schedule 20.05.2018
comment
Спецификация возвращаемого значения memcmp в стандарте ISO C не очень точна.   -  person M.M    schedule 20.05.2018
comment
Какая версия OSX, какая версия компилятора, какая конкретная команда компиляции?   -  person Eric Postpischil    schedule 20.05.2018
comment
@EricPostpischil Я обновил вопрос, чтобы отразить версии   -  person 1ShotSniper    schedule 21.05.2018
comment
Возможный обходной путь: #include <string.h>, за которым следует #undefine memcmp, по стандарту должен удалить любой макрос, скрывающий имя функции memcmp.   -  person Davislor    schedule 27.05.2018


Ответы (3)


Это ошибка компилятора. Компилятор неправильно оценивает вызовы memcmp, когда оба аргумента являются литералами. Когда memcmp действительно вызывается, он возвращает ожидаемый результат.

Следующее было протестировано с Apple LLVM версии 9.1.0 (clang-902.0.39.1) в macOS 10.13.4 (17E199). Я скомпилировал с помощью «clang -std=c11», либо с «-O0», либо с «-O3» для выбора уровня оптимизации и с «-S» для создания сборки.

Рассмотрим четыре альтернативных вызова memcmp:

    printf("%i\n", memcmp("\200", "\0", 1));

    printf("%i\n", memcmp((char[] ) { '\200' }, "\0", 1));

    printf("%i\n", memcmp((unsigned char[] ) { '\200' }, "\0", 1));

    char a[1] = { 128 };
    char b[1] = { 0 };
    printf("%i\n", memcmp(a, b, 1));

Для первых двух вызовов компилятор генерирует неправильную сборку, которая передает жестко заданное значение от -1 до printf. Нет вызова memcmp; он был оптимизирован, даже в версии «-O0». (В версиях «-O0» -1 кодируется как 4294967295, что эквивалентно в его контексте.) Когда memcmp вызывается со строковыми литералами или составными литералами, его возвращаемое значение известно во время компиляции, поэтому компилятор оценил Это. Однако сделал это неправильно.

Для третьего вызова компилятор генерирует неправильную сборку, которая передает жестко закодированное значение 1. Это говорит о том, что компилятор (неправильно) использует тип литерала в своей оценке.

Для четвертого вызова, в котором мы используем определенные объекты, не являющиеся литералами, версия «-O0» вызывает memcmp. При запуске программа выводит правильный результат, 128. Для версии «-O3» компилятор генерирует правильный ассемблер с жестко заданным значением 128. Таким образом, компилятор < strong>имеет алгоритм, который правильно оценивает memcmp во время компиляции, но он использовал другой ошибочный алгоритм для случаев с литералами.

Когда используется один литерал и один нелитерал, компилятор генерирует правильный код. Это объясняет, почему эта ошибка не была обнаружена и исправлена ​​ранее: вызовы memcmp с двумя литералами редки, а код, который делает то же самое и зависит от величины результата или использует символы с установленными старшими битами, встречается реже.

(Я сообщил об ошибке в Apple.)

person Eric Postpischil    schedule 27.05.2018

Похоже, в вашей конкретной реализации memcmp есть ошибка.

Я попробовал вашу программу на своей системе OSX/Darwin и получил положительное число. Так что в моей системе нет ошибки.

Как ни странно, поведение в моей системе различается в зависимости от того, использую ли я clang или gcc. Я думал, что они используют одни и те же библиотеки, но clang дает 128, а gcc дает 1. (Возможно, memcmp реализован как компилятор, встроенный в один или другой.)

Кроме того, кстати, man memcmp в моей системе не имеет предложения «Это поведение не требуется для C».

person Steve Summit    schedule 20.05.2018
comment
Это предложение находится на справочной странице memcmp(), а не strcmp(). У меня есть это в моей системе High Sierra. - person Barmar; 20.05.2018
comment
@Бармар Ура! Моя непреднамеренная опечатка сделала и без того запутанную ситуацию еще более запутанной. Я имел в виду man memcmp, у которого тоже нет предложения в моей системе (10.9.5). - person Steve Summit; 20.05.2018
comment
Что говорит нижний колонтитул? Мой говорит BSD June 4, 1993 BSD - person Barmar; 20.05.2018
comment
Это может зависеть от вашей версии XCode, откуда берутся справочные страницы библиотеки в MacOS. - person Barmar; 20.05.2018
comment
Мой нижний колонтитул идентичен вашему. Я говорю о том, что это поведение не требуется для C, и переносимый код должен зависеть только от знака возвращаемого значения. А у тебя это точно есть? Все любопытнее и любопытнее. - person Steve Summit; 20.05.2018
comment
Да, мой текст идентичен тому, что в вопросе. - person Barmar; 20.05.2018
comment
@Barmar Не то, чтобы это действительно имело значение, но: я наконец нашел источник. Я думаю, что дата в нижнем колонтитуле не имеет смысла; это, вероятно, происходит из пакета макросов man. Источник /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/share/man/man3/memcmp.3, mtime 1428272219, sum 53473. Тоже самое, но s/10.9.sdk/10.10.sdk/. - person Steve Summit; 21.05.2018
comment
Мой /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/share/man/man3/memcmp.3 mtime 1516569195 (21 января 2018 г.) - person Barmar; 21.05.2018
comment
Удивительно, но этот ответ неверен! memcmp в macOS правильно. Компилятор не прав! Он неправильно обрабатывает вызовы memcmp, когда оба аргумента являются литералами. Что объясняет, почему это не наблюдалось и не сообщалось ранее; вызовы memcmp, в которых оба аргумента являются литералами, а код использует символы с установленными старшими битами или зависит от величины результата, встречаются редко. Я подготовил новый ответ. - person Eric Postpischil; 27.05.2018

Это ошибка в мануале. Он описывает strcmp(), который прекращает сравнение, когда достигает нулевого байта в одной из строк, поскольку это признак конца строки; более длинная строка будет считаться большей ("foobar" больше, чем "foo"). Но memcmp() предназначен для сравнения произвольных областей памяти, а не строк, поэтому нулевые байты специально не обрабатываются.

Однако это не объясняет, почему memcmp() возвращает -1. Он должен сравнивать '\200' и '\0' и возвращать положительное значение. Кажется, что Дарвин memcmp() сравнивает их как signed char, а не как unsigned char, поэтому '\200' это -128, а не 128. Если первая строка имеет значение от "\200" до "\377", она возвращает этот неверный результат.

Когда я пробую ваш код в Linux, я получаю 1, а не -1. Так что это похоже на ошибку в библиотеке Дарвина. А также ошибка на странице руководства, так как там написано, что они сравниваются как unsigned char.

Я пробовал эту программу:

#include <stdio.h>
#include <string.h>

int main()
{
    printf("memcmp: %i\n", memcmp("\200", "\0", 1));
    printf("bcmp: %i\n", bcmp("\200", "\0", 1));
    printf("strcmp: %i\n", strcmp("\200", "\0"));
    return (0);
}

В Mac OS High Sierra он печатает:

memcmp: -1
bcmp: 128
strcmp: 128

в Debian Linux я получаю:

memcmp: 1
bcmp: 1
strcmp: 1

Упоминание строк нулевой длины на странице руководства также неверно. "\0abc" и "\0def" являются строками нулевой длины, поскольку строки логически заканчиваются нулевым байтом. Но они сравнивают разные с memcmp()

printf("memcmp: %i\n", memcmp("\0abc", "\0def", 4));
printf("bcmp: %i\n", bcmp("\0abc", "\0def", 4));
printf("strcmp: %i\n", strcmp("\0abc", "\0def"));

печатает:

memcmp: -1
bcmp: -3
strcmp: 0
person Barmar    schedule 20.05.2018
comment
Цитата в вопросе, похоже, ничего не говорит об остановке на \0, так откуда же сравнение с strcmp()? Насколько я вижу, в руководстве просто используется \0 в качестве примера значения и упоминаются строки нулевой длины, так как вы можете передать memcmp() нулевую длину. - person ilkkachu; 20.05.2018
comment
"\0abc" и "\0def" являются строками нулевой длины, но они должны отличаться для memcmp(). - person Barmar; 20.05.2018
comment
Ну, это просто кажется придирчивым способом сказать, что в руководстве не следует говорить о длине строки, а о значении, передаваемом функции в качестве длины. До сих пор нет упоминания об остановке на \0, которая, казалось бы, является важной особенностью strcmp(). - person ilkkachu; 20.05.2018
comment
Верно, в нем вообще не должно упоминаться о строках. Также неправильно, когда он говорит, что возвращает разницу между первыми отличающимися байтами. В моих тестах он всегда возвращает -1, 0 или 128. Обратите внимание, что bcmp() возвращает -3 в моем последнем примере. - person Barmar; 20.05.2018
comment
Этот ответ неверно истолковывает значение «строки» при обсуждении результата memcmp("\0abc", "\0def", 4). Хотя общепринятым языком при обсуждении C является использование слова «строка» для обозначения последовательности char, заканчивающейся нулем, это значение слова не диктуется стандартом C. C 2011 [N1570] сообщает нам, что string.h объявляет средства для «манипулирования массивами символьного типа» и «для определения длины массивов используются различные методы». Как используется в информатике, «строка» означает последовательность символов или, в более общем смысле, последовательность элементов из набора. - person Eric Postpischil; 20.05.2018
comment
Кроме того, справочные страницы документируют функции (и другие вещи) для общего использования, а не только для C, и их не следует читать как ограниченные стандартом C. Действительно, многие из них были впервые написаны до появления стандарта C, и нельзя ожидать, что они будут использовать ту же терминологию и соглашения. - person Eric Postpischil; 20.05.2018
comment
@EricPostpischil Строки C всегда ссылаются на последовательности символов, заканчивающиеся нулем, что не было нововведением комитета по стандартам. Вот почему существуют отдельные функции strXXX и memXXX. - person Barmar; 20.05.2018
comment
@Bamar: Как я уже писал, использование «строки» является неофициальным значением, не продиктованным стандартом C. Он может широко использоваться многими людьми, но он не является ни универсальным, ни обязательным. Интерпретируя memcmp справочную страницу как использующую «строку» таким образом, вы совершаете ошибку. Страница руководства memcmp правильно использует слово "строка" в его общем значении для компьютерных наук. - person Eric Postpischil; 20.05.2018
comment
Я программирую уже 40 лет и не могу припомнить, чтобы в литературе по C это слово использовалось в каком-либо ином значении, кроме неофициального. - person Barmar; 20.05.2018
comment
@Barmar: справочная страница memcmp не является литературой C. Это Unix-литература. Он документирует подпрограмму, которая является частью Unix. Некоторые реализации C используются в Unix для обеспечения той же функции, но документация C для этого находится в стандарте C. Страница руководства определяет функцию Unix, используя собственную терминологию, а стандарт C определяет функцию, используя терминологию C. - person Eric Postpischil; 20.05.2018
comment
Это литература Unix о функции C. C и Unix разрабатывались вместе, у них много терминологии. - person Barmar; 20.05.2018
comment
@Bamar: У них много общего. Они в чем-то различаются. Вы нашли разницу. - person Eric Postpischil; 20.05.2018
comment
Можете ли вы предоставить ссылку на любое использование строки, которая не означает последовательность символов, заканчивающуюся нулем? Я никогда не слышал, чтобы его использовали официально или неофициально каким-либо другим образом. - person Barmar; 20.05.2018
comment
@EricPostpischil Если я могу вмешаться: я думаю, что Бармар такой же эксперт, как и вы; вам не нужно читать ему лекции. Я думаю, что законное различие, которое вы двое обнаружили, заключается в одном мнении, между вашим и его. - person Steve Summit; 20.05.2018
comment
У нас есть две конкурирующие ситуации. Во-первых, на странице man слово «строка» используется в смысле, завершающемся нулем, и (а) неправильно описывается strcmp вместо memcmp, (б) неправильно указывается результат сравнения строк нулевой длины, когда memcmp передается ненулевая длина , и (c) не был зафиксирован по прошествии значительного времени. Во-вторых, на странице man слово «строка» используется в общем понимании информатики, и это правильно. Не вижу причин отдавать предпочтение первому толкованию. - person Eric Postpischil; 20.05.2018
comment
Страница руководства ошибочна во многих отношениях. Почему вы так щедро интерпретируете его использование строки? Если бы они имели в виду, когда n равно нулю, они могли бы просто сказать это. - person Barmar; 20.05.2018
comment
@Bamar: Википедия о «строке» в информатике. - person Eric Postpischil; 20.05.2018
comment
@Barmar: я интерпретирую это таким образом, потому что (а) при такой интерпретации страница верна, (б) это определение, используемое в информатике, и (в) я использовал другие языки программирования, учебники и сообщения, где это было это значение. - person Eric Postpischil; 20.05.2018
comment
@Bamar: Оказывается, это ошибка в компиляторе. Фактическая подпрограмма memcmp дает правильный результат, как задокументировано. - person Eric Postpischil; 27.05.2018