【问题标题】:How does strcmp() work?strcmp() 是如何工作的?
【发布时间】:2012-08-21 14:34:46
【问题描述】:

我一直在寻找答案。我要自己做一系列的字符串函数,比如my_strcmp()my_strcat()等。

strcmp() 是否通过两个字符数组的每个索引工作,如果 ASCII 值在两个字符串的相同索引处较小,则该字符串按字母顺序较大,因此返回 0 或 1 或 2?我想我问的是,它是否使用字符的 ASCII 值来返回这些结果?

任何帮助将不胜感激。

[修订]

好的,所以我想出了这个……它适用于所有情况,除非第二个字符串大于第一个字符串。

有什么建议吗?

int my_strcmp(char s1[], char s2[])
{   
    int i = 0;
    while ( s1[i] != '\0' )
    {
        if( s2[i] == '\0' ) { return 1; }
        else if( s1[i] < s2[i] ) { return -1; }
        else if( s1[i] > s2[i] ) { return 1; }
        i++;
    }   
    return 0;
}


int main (int argc, char *argv[])
{
    int result = my_strcmp(argv[1], argv[2]);

    printf("Value: %d \n", result);

    return 0;

}

【问题讨论】:

  • 你为什么不只看一个实现(glibc 或任何其他 - 搜索“strcmp 源代码”)? (对于返回值和规范,请阅读手册页或 POSIX。)
  • @DizzyChamp strcmp 使用lexicographical order

标签: c string


【解决方案1】:

这是我的版本,专为小型微控制器应用程序编写,符合 MISRA-C。 这段代码的主要目的是编写可读的代码,而不是大多数编译器库中的一行代码。

int8_t strcmp (const uint8_t* s1, const uint8_t* s2)
{
  while ( (*s1 != '\0') && (*s1 == *s2) )
  {
    s1++; 
    s2++;
  }

  return (int8_t)( (int16_t)*s1 - (int16_t)*s2 );
}

注意:代码假定为 16 位 int 类型。

【讨论】:

  • 注意:&amp;&amp; (*s2 != '\0') 不是必需的。
  • 此代码不正确:(*s1 != *s2) 应为 (*s1 == *s2)。为什么strcmp 应该返回int8_t?它被定义为返回一个int!最后的 return 有 3 个无用的演员表:return (*s1 &gt; *s2) - (*s1 &lt; *s2); 不假设 intchar 的大小。
  • 如果字符串可以包含非ASCII字符,则返回值(int8_t)( (int16_t)*s1 - (int16_t)*s2 );实际上是不正确的,因为两个字符的差异可能会超出int8_t的范围。你会得到实现定义的行为,并且可能会得到一个不正确的符号。
  • @chqrlie 确实,不知道 != 来自哪里。我从中获取 sn-p 的原始代码没有那个错误。固定。
  • @Lundin: 恐怕你弄错了:如果char 类型是8 位有符号的,那么字符文字'\377' 确实有类型int 但它的值是-1 (参见 C11 6.4.4.4 字符常量 pp9 到 13 以及等效示例 '\xFF')。关于字符串文字,C11 6.4.5 p4 明确指出 同样的考虑适用于字符串文字中序列的每个元素,就好像它在整数字符常量中一样......。因此,在同一平台上,字符串文字 "\377" 也被解析为具有值 -1 的单个字符。测试用例中是否还有其他问题?
【解决方案2】:

我在网上找到了这个。

http://www.opensource.apple.com/source/Libc/Libc-262/ppc/gen/strcmp.c

int strcmp(const char *s1, const char *s2)
{
    for ( ; *s1 == *s2; s1++, s2++)
        if (*s1 == '\0')
            return 0;
    return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1 : +1);
}

【讨论】:

    【解决方案3】:

    就是这样:

    int strcmp(char *str1, char *str2){
        while( (*str1 == *str2) && (*str1 != 0) ){
            ++*str1;
            ++*str2;
        }
        return (*str1-*str2);
    }
    

    如果你想要更快,你可以在类型前添加“注册”,如下所示: 注册字符

    然后,像这样:

    int strcmp(register char *str1, register char *str2){
        while( (*str1 == *str2) && (*str1 != 0) ){
            ++*str1;
            ++*str2;
        }
        return (*str1-*str2);
    }
    

    这样,如果可能的话,使用 ALU 的寄存器。

    【讨论】:

    • 您的实现不正确,OP 也是如此:返回值应该反映unsigned char 值的比较结果,而不是char。更重要的是,++*str1;++*str2; 应该只是 ++str1;++str2;
    【解决方案4】:

    这来自大师们自己(K&R,第 2 版,第 106 页):

    // strcmp: return < 0 if s < t, 0 if s == t, > 0 if s > t
    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];
    }
    

    【讨论】:

    • 将最后一条语句改为return ((unsigned char *)s[i] &lt; (unsigned char *)t[i]) ? -1 : +1;
    • @chqrlie 你的意思是((unsigned char *)s)[i] 等等。
    • @M.M:当然可以,或者return ((unsigned char)s[i] &lt; (unsigned char)t[i]) ? -1 : +1;
    【解决方案5】:

    这就是我实现 strcmp: 的方式 它是这样工作的: 它比较两个字符串的第一个字母,如果相同,则继续到下一个字母。如果不是,则返回相应的值。它非常简单易懂: #包括

    //function declaration:
    int strcmp(char string1[], char string2[]);
    
    int main()
    {
        char string1[]=" The San Antonio spurs";
        char string2[]=" will be champins again!";
        //calling the function- strcmp
        printf("\n number returned by the strcmp function: %d", strcmp(string1, string2));
        getch();
        return(0);
    }
    
    /**This function calculates the dictionary value of the string and compares it to another string.
    it returns a number bigger than 0 if the first string is bigger than the second
    it returns a number smaller than 0 if the second string is bigger than the first
    input: string1, string2
    output: value- can be 1, 0 or -1 according to the case*/
    int strcmp(char string1[], char string2[])
    {
        int i=0;
        int value=2;    //this initialization value could be any number but the numbers that can be      returned by the function
        while(value==2)
        {
            if (string1[i]>string2[i])
            {
                value=1;
            }
            else if (string1[i]<string2[i])
            {
                value=-1;
            }
            else
            {
                i++;
            }
        }
        return(value);
    }
    

    【讨论】:

    • 这个答案不正确。根据 C 规范,比较 string1[i]&gt;string2[i]string1[i]&lt;string2[i] 应该是 unsigned char 比较。
    • 此代码不正确,因为当字符串相等时,它会挂起程序,并且这样做会读取空终止符,因此可能越界,导致未定义的行为。
    【解决方案6】:

    这段代码等价,更短,更易读:

    int8_t strcmp (const uint8_t* s1, const uint8_t* s2)
    {
        while( (*s1!='\0') && (*s1==*s2) ){
            s1++; 
            s2++;
        }
    
        return (int8_t)*s1 - (int8_t)*s2;
    }
    

    我们只需要测试 s1 的结尾,因为如果我们在 s1 的结尾之前到达 s2 的结尾,循环将终止(因为 *s2 != *s1)。

    如果我们只使用 7 位(纯 ASCII)字符,则返回表达式在每种情况下都会计算正确的值。 需要仔细考虑为 8 位字符生成正确的代码,因为存在整数溢出的风险。

    【讨论】:

    • 为什么要发布对“仅使用 7 位(纯 ASCII)字符”正确的解决方案?最好发布一个符合 C 规范的解决方案。
    • 在最后一行转换为int8_t 是多余的,因为无论如何每个操作数都会被隐式提升为int
    • 如果返回类型为int,则返回表达式对于 8 位字符是可以的。演员表没用。整数溢出没有风险。事实上,如果char 类型与int 类型具有相同的大小,则您不能使用此表达式,这可能会出现在某些 DSP 架构中。但是由于您将返回值定义为非标准int8_t,因此如果两个字符之间的距离大于127,则不能使用此表达式。
    【解决方案7】:

    strcmp 的伪代码“实现”类似于:

    define strcmp (s1, s2):
        p1 = address of first character of str1
        p2 = address of first character of str2
    
        while contents of p1 not equal to null:
            if contents of p2 equal to null: 
                return 1
    
            if contents of p2 greater than contents of p1:
                return -1
    
            if contents of p1 greater than contents of p2:
                return 1
    
            advance p1
            advance p2
    
        if contents of p2 not equal to null:
            return -1
    
        return 0
    

    基本上就是这样。依次比较每个字符,然后根据该字符决定第一个字符串还是第二个字符串更大。

    只有当字符相同时才移动到下一个字符,如果所有字符相同,则返回零。

    请注意,您不一定会得到 1 和 -1,规范说任何正值或负值都足够,因此您应始终使用 &lt; 0&gt; 0== 0 检查返回值。

    把它变成真正的 C 语言会相对简单:

    int myStrCmp (const char *s1, const char *s2) {
        const unsigned char *p1 = (const unsigned char *)s1;
        const unsigned char *p2 = (const unsigned char *)s2;
    
        while (*p1 != '\0') {
            if (*p2 == '\0') return  1;
            if (*p2 > *p1)   return -1;
            if (*p1 > *p2)   return  1;
    
            p1++;
            p2++;
        }
    
        if (*p2 != '\0') return -1;
    
        return 0;
    }
    

    另外请记住,字符上下文中的“更大”不一定基于 所有 字符串函数的简单 ASCII 排序。

    C 有一个称为“语言环境”的概念,它指定(除其他外)排序规则或基础字符集的顺序,例如,您可能会发现字符 aáàä 都被认为是相同的。这将发生在像 strcoll 这样的函数上。

    【讨论】:

    • strcmp 与语言环境无关。它将字节值比较为unsigned char
    • strcoll 和 strxfrm 除外,它们对区域设置敏感。 strcoll 基本上是区域感知 strcmp,而 strxfrm 转换字符串以便 strcmp 执行区域感知比较。当然,如果可能的话,最好还是使用真正的国际化库。
    • 呃,我并不是说所有(甚至大多数)strXXX 函数都使用语言环境。我认为并非所有人都忽略它。正如我所指出的,strcoll 是使用它的一种,而 nneonneo 也提到了 strxfrm。我提到这一点是因为 OP 声明“我将制作一个 series 我自己的字符串函数”。该系列可能包含也可能不包含所有 ISO C strXXX 函数,我只是认为涵盖所有可能性更安全。
    • nevermind - "由比较函数 memcmp、strcmp 和 strncmp 返回的非零值的符号由第一对字符的值之间的差的符号确定(均解释为无符号char) 在被比较的对象上不同。” ...很高兴知道
    • 注意:if (*p2 == '\0') return 1; 不是必需的。
    【解决方案8】:

    它使用字符的字节值,如果第一个字符串出现在第二个字符串之前(按字节值排序),则返回负值,如果相等则返回零,如果第一个字符串出现在第二个之后,则返回正值。由于它对字节进行操作,因此它不支持编码。

    例如:

    strcmp("abc", "def") < 0
    strcmp("abc", "abcd") < 0 // null character is less than 'd'
    strcmp("abc", "ABC") > 0 // 'a' > 'A' in ASCII
    strcmp("abc", "abc") == 0
    

    更准确地说,如strcmp Open Group specification中所述:

    非零返回值的符号应由在被比较的字符串中不同的第一对字节(均解释为类型无符号字符)的值之间的差异符号确定。

    注意,返回值可能不等于这个差值,但会带有相同的符号。

    【讨论】:

    • 最佳答案在这里,实际上没有人谈到这个 null 字符小于任何其他字符
    【解决方案9】:

    这里是BSD implementation

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

    一旦两个字符不匹配,它只返回这两个字符之间的差异。

    【讨论】:

    • 值得注意的是,它不会总是返回两个不同字符之间的差异;如果符号与字节之间的差异相同,则实际上允许返回任何整数。对这个实现细节的误解(在相关的 memcmp 函数中)导致了现实世界中的security vulnerability in MySQL
    • @nneonneo 这是一个很好的观点。通过“字符之间的差异”实现它是好的,但作为用户依赖它是不好的。
    • 和往常一样,使用RTFM之前的一个函数。甚至 C 标准也非常清楚 strcmp() 的行为方式:The strcmp function returns an integer greater than, equal to, or less than zero, accordingly as the string pointed to by s1 is greater than, equal to, or less than the string pointed to by s2. 就是这样,没有进一步的保证。
    猜你喜欢
    • 2010-11-01
    • 2016-06-23
    • 2019-01-26
    • 1970-01-01
    • 1970-01-01
    • 2016-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多