Если использовать правильно, strcmp
не опасен. тем не мение код, вызывающий это, может быть. Ограничивая возможность появления ошибок, я мог бы в больших командах «диктовать» какой-то упреждающий удар по ошибкам из-за плохого кода вызова. Кажется, проблема номер один — это сравнение строк, не оканчивающихся нулем. Суть моего решения такова:
// ----------------------------------------------
//
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define VERIFY(X) do { if (!(X)) { perror(#X); exit(0); } } while(0)
#define B(X_) (X_) ? "true" : "false"
#define P(F,X_) fprintf( stdout, "n%4d : %16s : " F, __LINE__, #X_, (X_))
// https://stackoverflow.com/a/4415646/10870835
#undef COUNT_OF
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) %
sizeof(0[x])))))
// safer strcmp
static int safer_strcmp_ (
const size_t N, const size_t M,
const char (*L)[N],
const char (*R)[M])
{
VERIFY( L );
VERIFY( R );
VERIFY( 0 == ((*L)[N]) );
VERIFY( 0 == ((*R)[M]) );
const char * str1 = &(*L)[0] ;
const char * str2 = &(*R)[0] ;
VERIFY( str1 );
VERIFY( str2 );
// https://codereview.stackexchange.com/a/31954/179062
while (*str1 && *str1 == *str2) {
str1++;
str2++;
}
return *str1 - *str2;
}
#undef COMPARE
// macro front is OK for string literals
// COUNT_OF includes the EOS char, hence the -1
#define COMPARE(A,B)
safer_strcmp_( COUNT_OF(A) - 1 ,COUNT_OF(B) - 1, &A, &B )
int main(void) {
// does not compile: P("%d", COMPARE( NULL,"")) ;
P("%d", COMPARE("","")) ;
P("%d", COMPARE("A","AA")) ;
P("%d", COMPARE("A","A")) ;
P("%d", COMPARE("AA","A")) ;
P("%d", COMPARE("B","A")) ;
return 0 ;
}
https://godbolt.org/z/6bbeWazYW
По сути, код просто проверяет, правильно ли завершены обе строки. Это макрос для его вызова с помощью литералов:
Макрос скрывает сложность выполнения этого вызова. При использовании переменных все, что я могу придумать, включает использование strlen
/ strnlen
и это противоречит цели.
Есть ли способ использовать вышеуказанное с переменными?
[EDIT]
Весь смысл этой функции заключается в использовании «указателя на массив символов» в сочетании с типами VL вместо char *
. Даже до этого весь смысл этой функции состоит в том, чтобы попытаться остановить незаметные ошибки, когда происходит «невозможное»: незавершенные строки. «Правильный поступок» может заключаться в том, чтобы прекратить появление char *
, но это невозможно из-за устаревших и сторонних проблем.
2 ответа
Незаметная функциональная ошибка
return *str1 - *str2;
может вернуть неправильное значение знака, когда char
значения отрицательные.
Функции строковой библиотеки C определяют:
Для всех функций в этом подпункте каждый символ должен интерпретироваться так, как если бы он имел тип
unsigned char
(и, следовательно, все возможные представления объекта действительны и имеют различное значение). C17 § 7.24.1 3
Отремонтированный сравните ниже.
Педантичность: этот код также правильно обрабатывает редкие дополнения, отличные от 2, и когда CHAR_MAX > INT_MAX
в отличие от ОП.
const unsigned char *u1 = (const unsigned char *) str1;
const unsigned char *u2 = (const unsigned char *) str2;
while (*u1 && *u1 == *u2) {
u1++;
u2++;
}
return (*u1 > *u2) - (*u1 < *u2);
Что такое нить?
OP сообщает: «Кажется, проблема номер один — это сравнение строк, не оканчивающихся нулем». Библиотека C определяет нить ниже. А нить всегда содержит нулевой символ ' '
.
А нить представляет собой непрерывную последовательность символов, которая заканчивается первым нулевым символом и включает его.
Конечно, в массиве символов может отсутствовать нулевой символ и поэтому эта функция может иметь значение с этими не-строками.
Звонок COMPARE()
с указателем, а не с массивом приводит к неприятностям, в отличие от strcmp()
.
Макрос безопасен только при передаче строковых литералов, он небезопасен, когда вы передаете ему указатели на строки. В частности, учитывая char *s
, результат COUNT_OF(s)
равно 8 на 64-битных машинах. Это означает, что если вы передадите ему указатели на строки короче 8 символов (включая NUL-байт), он будет читать после конца строки. Учитывая, что весь смысл упражнения заключается в обеспечении большей безопасности, чем при обычном strcmp()
, этот макрос не выполняет обещание. Макрос также не очень полезен для строковых литералов, поскольку компилятор уже гарантирует, что литералы оканчиваются NUL.
Заставить этот код работать с переменными невозможно. Единственный способ проверить длину строки, на которую указывает char *
переменная должна вызвать strlen()
на нем, и это предполагает, что строка правильно завершена NUL. Единственный способ сделать это — заставить вызывающего абонента передать длину строки, но это просто перекладывает бремя на вызывающего.
На мой взгляд, лучшая стратегия — просто позвонить вызывающему. strcmp()
когда он знает, что оба аргумента являются правильными строками с завершающим нулем, и strncmp()
если ему известен размер одной или обеих строк.
Спасибо за трезвый ответ.
— DBJDBJ