Что не так с strcmp?

В ответах на вопрос Чтение строки и ее сравнение C несколько человек не одобряли использование strcmp(), говоря что-то вроде

Я также настоятельно рекомендую вам привыкнуть к использованию strncmp() сейчас, ... чтобы избежать многих проблем в будущем.

или (в Почему сравнение строк не удается?)

Убедитесь, что вы используете strncmp, а не strcmp. strcmp крайне небезопасен.

О каких проблемах они говорят?

Причина scanf() со строковыми спецификаторами и gets() настоятельно не рекомендуется, потому что они почти неизбежно приводят к уязвимостям переполнения буфера. Однако невозможно переполнить буфер с помощью strcmp(), верно?

«Переполнение буфера или переполнение буфера — это аномалия, при которой программа при записи данных в буфер выходит за пределы буфера и перезаписывает соседнюю память».

( -- Википедия: переполнение буфера).

Поскольку функция strcmp() никогда не записывает ни в какой буфер, функция strcmp() не может вызвать переполнение буфера, верно?

По какой причине люди не одобряют использование strcmp() и вместо этого рекомендуют strncmp()?


person David Cary    schedule 22.06.2014    source источник
comment
Почему бы вам не спросить этих людей?   -  person Kerrek SB    schedule 22.06.2014
comment
Поверьте мне. strncmp() не лучше (или хуже), чем strcmp().   -  person P.P    schedule 22.06.2014
comment
Комментарии к связанной статье SO в основном являются нежелательными.   -  person M.M    schedule 23.06.2014
comment
Использование strncmp для защиты от строк, которые не завершаются нулем, просто устраняет основную проблему, заключающуюся в том, что у вас есть незавершенная строка. Это просто испортит следующую функцию, которая предполагает, что она завершается нулем.   -  person Schwern    schedule 27.03.2017


Ответы (3)


Хотя strncmp может предотвратить переполнение буфера, его основная цель не безопасность. Скорее, он существует для случая, когда нужно сравнить только первые N символов строки (правильно возможно заканчивающейся NUL).

На справочной странице:

Функция strcmp() сравнивает две строки s1 и s2. Он возвращает целое число, меньшее, равное или большее нуля, если найдено, что s1 соответственно меньше, соответствует или больше s2.

Функция strncmp() аналогична, за исключением того, что она сравнивает только первые (не более) n байтов s1 и s2.

Обратите внимание, что strncmp в этом случае нельзя заменить простым memcmp, потому что вам все равно нужно воспользоваться его поведением остановки при NUL, если одна из строк короче n.

Если strcmp вызывает переполнение буфера, то верно одно из двух:

  1. Ожидается, что ваши данные не будут заканчиваться NUL, и вместо этого вы должны использовать memcmp.
  2. Ожидается, что ваши данные будут завершаться NUL, но вы уже облажались при заполнении буфера, каким-то образом не завершая его NUL.

Обратите внимание, что чтение после конца буфера по-прежнему считается переполнением буфера. Хотя это может казаться безобидным, это может быть так же опасно, как написание после конца.

Читать, писать, исполнять... не важно. Любая ссылка памяти на непредусмотренный адрес является поведением undefined. В наиболее очевидном сценарии вы пытаетесь получить доступ к странице, которая не сопоставлена ​​с адресным пространством вашего процесса, что приводит к ошибке страницы и последующему SIGSEGV. В худшем случае вы иногда сталкиваетесь с байтом \0, но иногда вы сталкиваетесь с каким-то другим буфером, вызывая непостоянное поведение программы.

person Jonathon Reinhart    schedule 22.06.2014
comment
Я не понимаю, как strcmp() может вызвать переполнение буфера, даже если обе эти вещи верны. Не могли бы вы сказать еще несколько слов о том, что именно идет не так? - person David Cary; 23.06.2014
comment
Представьте, что у вас есть char buf[100], в котором каждый символ равен 'a' (он не заканчивается NUL). Если вы передадите этот буфер в strcmp (при условии, что другой параметр является более длинной строкой, то strcmp продолжит сравнение с buf[100] и так далее, переполнив буфер. - person Jonathon Reinhart; 23.06.2014
comment
Я вижу, как запись после конца буфера вызывает проблемы. Но strcmp() этого не делает, верно? Не могли бы вы добавить несколько слов к своему ответу о том, что именно пойдет не так, если strcmp() продолжит чтение за концом буфера? - person David Cary; 23.06.2014
comment
Читать, писать, исполнять... не важно. Любая ссылка памяти на непредусмотренный адрес является поведением undefined. В наиболее очевидном сценарии вы пытаетесь получить доступ к странице, которая не сопоставлена ​​с адресным пространством вашего процесса, что приводит к ошибке страницы и последующему SIGSEGV. В худшем случае вы иногда сталкиваетесь с \0 байтом, но иногда вы сталкиваетесь с каким-то другим буфером, вызывая непостоянное поведение программы. - person Jonathon Reinhart; 23.06.2014
comment
Хорошая точка зрения. Пожалуйста, нажмите кнопку редактирования выше и добавьте ее в свой ответ. Я много раз программировал на машинах, которые никогда не выдают ошибки страниц, так что эта конкретная проблема ошибок страниц никогда не возникает на этих машинах, но это именно то, о чем я хочу знать, поэтому мой C можно переносить на машины, где такого рода вещи могут и случаются. - person David Cary; 23.06.2014
comment
Если вы хотите обеспечить переносимость, вы должны обеспечить корректность. Независимо от того, какая машина, чтение после конца буфера приведет к неопределенному поведению. Процессор может спокойно читать нули, или контроллер памяти может загореться. - person Jonathon Reinhart; 23.06.2014
comment
он существует для случая, когда нужно сравнить только первые N символов строки (правильно заканчивающейся NUL). неправильно. Из спецификации C Функция strncmp возвращает целое число ... соответственно как массив, возможно заканчивающийся нулем, на который указывает s1. Ни s1, ни s2 из int strncmp(const char *s1, const char *s2, size_t n); не обязательно должны быть строками C. Независимо друг от друга они могут быть просто строками или массивами char без правильного завершения NUL. - person chux - Reinstate Monica; 23.06.2014
comment
@chux Хм, кажется, мне придется пересмотреть свой ответ. Спасибо за это. - person Jonathon Reinhart; 23.06.2014

Строка по определению представляет собой «непрерывную последовательность символов, заканчивающуюся первым нулевым символом и включающую его».

Единственный случай, когда strncmp() будет безопаснее, чем strcmp(), это когда вы сравниваете два символьных массива как строки, вы уверены, что оба массива имеют длину не менее n байт (третий аргумент передается в strncmp()), и вы не уверен, что оба массива содержат строки (т. е. содержат '\0' нулевой символ конца).

В большинстве случаев ваш код (если он правильный) гарантирует, что любые массивы, которые должны содержать строки с завершающим нулем, на самом деле содержат строки с завершающим нулем.

Добавление n в strncmp() не является волшебной палочкой, которая делает небезопасный код безопасным. Он не защищает от нулевых указателей, неинициализированных указателей, неинициализированных массивов, неправильного значения n или просто передачи неверных данных. Вы можете выстрелить себе в ногу с любой функцией.

И если вы пытаетесь вызвать strcmp или strncmp с массивом, который, как вы думали, содержит строку с завершающим нулем, но на самом деле это не так, то в вашем коде уже есть ошибка. Использование strncmp() может помочь вам избежать немедленных симптомов этой ошибки, но не исправит ее.

person Keith Thompson    schedule 23.06.2014

strcmp сравнивает две строки посимвольно до тех пор, пока не будет обнаружено различие или не будет найдено \0 в одной из них.

С другой стороны, strncmp позволяет ограничить количество сравниваемых символов, поэтому, если строки не заканчиваются на \0, функция не будет продолжать проверку после достижения предела размера.

Представьте, что произойдет, если вы сравниваете две строки в этих двух областях памяти:

0x40, 0x41, 0x42,... 0x40, 0x41, 0x42,...

И вас интересуют только два первых символа. Каким-то образом \0 было удалено из конца строк, а третий байт совпадает в двух областях. strncmp не будет сравнивать этот третий байт, если параметр num равен 2.

EDIT Как показывают комментарии ниже, эта ситуация возникла из-за неправильного или очень конкретного использования языка.

person Pablo Francisco Pérez Hidalgo    schedule 22.06.2014
comment
Если вы хотите сравнить области памяти, используйте memcmp. В C строка представляет собой последовательность символов, заканчивающуюся нулем. Если у вас есть строки, используйте strcmp. Если вы этого не сделаете, не делайте этого. - person Kerrek SB; 22.06.2014
comment
Конечно, strcmp сделает сравнение безопасным, но на самом деле это просто откладывание проблемы. Ваша строка, не заканчивающаяся нулем, позже вызовет неопределенное поведение в вашей программе. - person Oliver Charlesworth; 22.06.2014
comment
@OliCharlesworth Я согласен, просто хотел указать случай, когда strncmp безопаснее использовать, чем strcmp - person Pablo Francisco Pérez Hidalgo; 22.06.2014
comment
Насколько я понимаю, strncmp не существует для безопасности, а я хочу сравнить первые N символов этих строк. - person Jonathon Reinhart; 22.06.2014
comment
@JonathonReinhart: Вы должны сделать это ответом. - person Oliver Charlesworth; 22.06.2014
comment
Вещи, которые не заканчиваются на \0, не являются строками - person M.M; 23.06.2014