Мой более безопасный strcmp

Если использовать правильно, 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 ответа
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().

  • Спасибо за трезвый ответ.

    — DBJDBJ

Макрос безопасен только при передаче строковых литералов, он небезопасен, когда вы передаете ему указатели на строки. В частности, учитывая char *s, результат COUNT_OF(s) равно 8 на 64-битных машинах. Это означает, что если вы передадите ему указатели на строки короче 8 символов (включая NUL-байт), он будет читать после конца строки. Учитывая, что весь смысл упражнения заключается в обеспечении большей безопасности, чем при обычном strcmp(), этот макрос не выполняет обещание. Макрос также не очень полезен для строковых литералов, поскольку компилятор уже гарантирует, что литералы оканчиваются NUL.

Заставить этот код работать с переменными невозможно. Единственный способ проверить длину строки, на которую указывает char * переменная должна вызвать strlen() на нем, и это предполагает, что строка правильно завершена NUL. Единственный способ сделать это — заставить вызывающего абонента передать длину строки, но это просто перекладывает бремя на вызывающего.

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

  • Все верно .. Отсюда вопрос: «Как это сделать с переменными».

    — DBJDBJ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *