Есть ли компилятор и библиотека, где strcmp () возвращает значения, отличные от -1 0 и 1? - PullRequest
1 голос
/ 17 января 2020

Хотя здравый смысл и литература ясны в отношении поведения strcmp():

int strcmp( const char *lhs, const char *rhs );

Отрицательное значение, если lhs появляется перед rhs в лексикографическом order.

Ноль, если lhs и rhs сравниваются равными.

Положительное значение, если lhs появляется после rhs в лексикографическом порядке.

Я не могу заставить его возвращать какие-либо значения, кроме -1, 0 и 1.

Конечно, это правда, что поведение соответствует определению , но я ожидал, что значения будут больше или меньше 1 или -1, так как определение утверждает, что результаты будут <0, 0 или >0, а не -1, 0 или 1.

Я протестировал это в нескольких компиляторах и библиотеках с одинаковыми результатами. Я хотел бы видеть пример, где это не так.

пример кода

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


int main() 
{  
   printf("%d ", strcmp("a", "a"));
   printf("%d ", strcmp("abc", "aaioioa"));
   printf("%d ", strcmp("eer", "tsdf"));
   printf("%d ", strcmp("cdac", "cdac"));
   printf("%d ", strcmp("zsdvfgh", "ertgthhgj"));
   printf("%d ", strcmp("abcdfg", "rthyuk"));
   printf("%d ", strcmp("ze34", "ze34"));
   printf("%d ", strcmp("er45\n", "io\nioa"));
   printf("%d", strcmp("jhgjgh", "cdgffd"));
}

Result: 0 1 -1 0 1 -1 0 -1 1

Ответы [ 4 ]

4 голосов
/ 17 января 2020

Стандарт C четко гласит (C11 §7.24.4.2 Функция strcmp ):

Функция strcmp возвращает целое число больше чем, равное, или меньше нуля, соответственно, так как строка, на которую указывает s1, больше, равна или меньше, чем строка, на которую указывает s2.

Она не говорит, насколько больше или меньше чем ноль, результат должен быть; функция, которая всегда возвращает -1, 0 или +1, соответствует стандарту; так же как и функция, которая иногда возвращает значения с величиной, превышающей 1, например -27, 0, +35. Если ваш код должен соответствовать стандарту C, он не должен принимать какой-либо набор результатов; он может только предполагать, что знак результата верен.

Здесь приведена реализация strcmp() - названная здесь str_cmp(), так что результат можно сравнить с strcmp() - который не возвращает -1 или +1:

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

static int str_cmp(const char *s1, const char *s2)
{
    while (*s1 == *s2 && *s1 != '\0')
        s1++, s2++;
    int c1 = (int)(unsigned char)*s1;
    int c2 = (int)(unsigned char)*s2;
    return (c1 - c2);
}

int main(void) 
{  
   printf("%d ", strcmp("a", "a"));
   printf("%d ", strcmp("abc", "aAioioa"));
   printf("%d\n", strcmp("eer", "tsdf"));

   printf("%d ", str_cmp("a", "a"));
   printf("%d ", str_cmp("abc", "aAioioa"));
   printf("%d\n", str_cmp("eer", "tsdf"));
   return 0;
}

При запуске на Ma c (macOS Mojave 10.14.6; G CC 9.2.0; Xcode 11.13.1), я получаю вывод:

0 1 -1
0 33 -15

Я немного изменил ваши данные - "aaioioa" стал "aAioioa". Общий результат ничем не отличается (но значение 33 больше, чем вы получили бы с исходной строкой) - возвращаемое значение меньше, равно или больше нуля, как требуется.

str_cmp() функция является законной реализацией и слабо основана на исторически распространенной реализации strcmp(). Он немного больше заботится о возвращаемом значении, но вы можете найти два его небольших варианта на стр. 106 Брайана В. Кернигана и Денниса М. Ричи C Язык программирования, 2nd Edn (1988) - один с использованием индексирование массива, другое с использованием указателей:

int strcmp(char *s, char *t)
{
    int i;
    for (i = 0; s[i] == t[i]; i++)
        if (s[i] == '\0')
            return 0;
    return s[i] - t[i];
}

int strcmp(char *s, char *t)
{
    for ( ; *s == *t; s++, t++)
        if (*s == '\0')
            return 0;
    return *s - *t;
}

Код K & R может не вернуть ожидаемый результат, если простой тип char подписан и если одна из строк содержит «символы с акцентом», символы из диапазон -128 .. -1 (или 0x80 .. 0xFF при просмотре в виде значений без знака). Приведение в моем коде str_cmp() обрабатывает данные как unsigned char (через приведение); (int) приведение не является действительно необходимым из-за назначений. Вычитание двух unsigned char значений, преобразованных в int, дает результат в диапазоне -255 .. +255. Однако современные версии библиотеки C не используют подобное прямое вычитание, если они возвращают только -1, 0 или +1.

Обратите внимание, что стандарт C11 § 7.24.4 Функции сравнения строк говорят:

Знак ненулевого значения, возвращаемого функциями сравнения memcmp, strcmp и strncmp, определяется знаком Разница между значениями первой пары символов (оба интерпретируются как unsigned char), которые отличаются в сравниваемых объектах.

Вы можете посмотреть Как проверить, соответствует ли значение строка? . Схема там показывает:

if (strcmp(first, second) == 0)    // first equal to second
if (strcmp(first, second) <= 0)    // first less than or equal to second
if (strcmp(first, second) <  0)    // first less than second
if (strcmp(first, second) >= 0)    // first greater than or equal to second
if (strcmp(first, second) >  0)    // first greater than second
if (strcmp(first, second) != 0)    // first unequal to second

Обратите внимание, что при сравнении с нулем используется тот же оператор сравнения, что и при выполнении теста.

Вы могли бы (но, вероятно, не должны 't) напишите:

if (strcmp(first, second) <= -1)    // first less than second
if (strcmp(first, second) >= +1)    // first greater than second

Вы все равно получите те же результаты, но это не имеет смысла; всегда сравнение с нулем проще и более равномерно.

Вы можете получить результат -1, 0, +1, используя:

unsigned char c1 = *s1;
unsigned char c2 = *s2;
return (c1 > c2) - (c1 < c2);

Для неограниченных целых чисел (вместо целых чисел, ограниченных 0. 255), это безопасно, поскольку позволяет избежать целочисленных переполнений, тогда как вычитание дает неверный результат. Для ограниченных целых чисел, связанных с 8-разрядными символами, переполнение при вычитании не является проблемой.

4 голосов
/ 17 января 2020

В спецификации сказано, что числа должны быть отрицательными, нулевыми или положительными, но это не фиксирует необходимое точное значение. Сама библиотека может вести себя более определенным образом c.

spe c означает, что подобный код технически недопустим:

if (strcmp(a, b) == 1)

Это может "работать на моей машине" но не кто-то другой, кто использует другую библиотеку.

Где вы должны писать:

if (strcmp(a, b) > 0)

Это все, что на самом деле означает: ожидайте значения, отличные от просто 1 / -1 и код соответственно.

2 голосов
/ 17 января 2020

Пожалуйста, перечитайте этот бит

Отрицательное значение, если lhs появляется перед rhs в лексикографическом порядке.

Достаточно ли -1, чтобы это утверждение было верным?

Ноль, если lhs и rhs сравниваются равными.

Положительное значение, если lhs появляется после rhs в лексикографическом порядке.

Достаточно ли 1, чтобы это утверждение было верным?

Таким образом, пример кода действует в соответствии со спецификацией c.

EDIT

Просто проверьте возвращаемое значение на ноль, меньше нуля или больше нуля. В соответствии с spe c это должно работать во всех реализациях.

EDIT 2

Я думаю, что это выполнит spe c - не проверял :-(

 for (size_t i = 0; s1[i] && s2[i] &&s1[i] == s2[i]; ++i) {
     // Empty
   }
   return s2[i] - s1[i]; // This may be the wrong way around

Это вернет значения, отличные от 1, -1 или 0.

1 голос
/ 23 апреля 2020

Вот несколько примеров C библиотек с strcmp() реализациями, которые не всегда возвращают -1, 0 или +1:

Bioni c lib c имеет реализацию на основе BSD strcmp():

int
strcmp(const char *s1, const char *s2)
{
    while (*s1 == *s2++)
        if (*s1++ == 0)
            return (0);
    return (*(unsigned char *)s1 - *(unsigned char *)--s2);
}

Dietlib c делает то же самое. Это даже несовместимая версия, если она настроена для WANT_SMALL_STRING_ROUTINES:

int
strcmp (const char *s1, const char *s2)
{
#ifdef WANT_SMALL_STRING_ROUTINES
    while (*s1 && *s1 == *s2)
        s1++, s2++;
    return (*s1 - *s2);
#else
    // a more advanced, conforming implementation that tests multiple characters
    // at a time but still return the difference of characters as unsigned bytes
#endif
}

Glib c имеет эту реализацию strcmp в своем каталоге generic, используемом для exoti c архитектуры:

int
strcmp (p1, p2)
     const char *p1;
     const char *p2;
{
  register const unsigned char *s1 = (const unsigned char *) p1;
  register const unsigned char *s2 = (const unsigned char *) p2;
  unsigned reg_char c1, c2;

  do
    {
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0')
    return c1 - c2;
    }
  while (c1 == c2);

  return c1 - c2;
}

Musl C Библиотека имеет очень компактную реализацию:

int strcmp(const char *l, const char *r)
{
    for (; *l==*r && *l; l++, r++);
    return *(unsigned char *)l - *(unsigned char *)r;
}

newlib имеет эту реализацию :

int
_DEFUN (strcmp, (s1, s2),
    _CONST char *s1 _AND
    _CONST char *s2)
{
#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__)
  while (*s1 != '\0' && *s1 == *s2)
    {
      s1++;
      s2++;
    }

  return (*(unsigned char *) s1) - (*(unsigned char *) s2);
#else
  // a more advanced approach, testing 4 bytes at a time, still returning the difference of bytes
#endif
}

Многие альтернативные библиотеки C, похоже, следуют одному и тому же шаблону и возвращают разницу в байтах, которая соответствует спецификации. Но протестированные вами реализации, похоже, последовательно возвращают -1, 0 или +1. Не надейся на это. Он может измениться в будущих выпусках или даже с той же системой, использующей разные флаги компиляции.

...