strchr实现如何工作

Mar*_*arc 18 c pointers const strchr

我试着编写自己的strchr()方法实现.

它现在看起来像这样:

char *mystrchr(const char *s, int c) {
    while (*s != (char) c) {
        if (!*s++) {
            return NULL;
        }
    }
    return (char *)s;
}
Run Code Online (Sandbox Code Playgroud)

最后一行原来是

return s;
Run Code Online (Sandbox Code Playgroud)

但这不起作用,因为s是const.我发现需要这个演员(char*),但老实说我不知道​​我在那里做什么:(有人可以解释一下吗?

Kei*_*son 19

我相信这实际上是C标准对strchr()函数定义的一个缺陷.(我会很高兴被证明是错的.)(回答这些评论,它是否真的是一个缺陷是有争议的;恕我直言,它仍然是糟糕的设计.它可以安全使用,但它太容易使用它不安全.)

这是C标准所说的:

char *strchr(const char *s, int c);
Run Code Online (Sandbox Code Playgroud)

和strchr函数定位的第一次出现Ç (转化为焦炭)在字符串由指向小号.终止空字符被认为是字符串的一部分.

这意味着这个程序:

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

int main(void) {
    const char *s = "hello";
    char *p = strchr(s, 'l');
    *p = 'L';
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

即使它小心地将指向字符串文字的指针定义为指向,也有未定义的行为,因为它修改了字符串文字.gcc,至少,不会对此发出警告,程序会因为分段错误而死亡.const char

问题是strchr()接受一个const char*参数,这意味着它承诺不修改s指向的数据- 但它返回一个plain char*,允许调用者修改相同的数据.

这是另一个例子; 它没有未定义的行为,但它悄悄地修改了一个const没有任何强制转换的限定对象(进一步认为,我认为它具有未定义的行为):

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

int main(void) {
    const char s[] = "hello";
    char *p = strchr(s, 'l');
    *p = 'L';
    printf("s = \"%s\"\n", s);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我认为,这意味着(回答你的问题)C实现strchr()必须将其结果转换为将其转换const char*char*或执行等效的操作.

这就是为什么C++在对C标准库进行的少量更改之一中取代strchr()了两个同名的重载函数:

const char * strchr ( const char * str, int character );
      char * strchr (       char * str, int character );
Run Code Online (Sandbox Code Playgroud)

当然C不能这样做.

另一种方法是strchr用两个函数替换,一个取a const char*并返回a const char*,另一个取a char*并返回a char*.不像在C++中,这两个函数必须有不同的名称,也许strchrstrcchr.

(从历史上看,conststrchr()已经定义之后添加到C中.这可能是在strchr()不破坏现有代码的情况下保留的唯一方法.)

strchr()是不是唯一有这个问题的C标准库函数.受影响的功能列表(我认为此列表已完成,但我不保证)是:

void *memchr(const void *s, int c, size_t n);
char *strchr(const char *s, int c);
char *strpbrk(const char *s1, const char *s2);
char *strrchr(const char *s, int c);
char *strstr(const char *s1, const char *s2);
Run Code Online (Sandbox Code Playgroud)

(全部申报<string.h>)和:

void *bsearch(const void *key, const void *base,
    size_t nmemb, size_t size,
    int (*compar)(const void *, const void *));
Run Code Online (Sandbox Code Playgroud)

(声明<stdlib.h>).所有这些函数都使用指向const数据的指针,该数据指向数组的初始元素,并返回const指向该数组元素的非指针.

  • 好吧,虽然有人可能认为它是一个"缺陷",但它仍然是故意完成的,以便允许该函数同时使用const和非const数据.这样做在C语言中非常惯用(正如我在回答中所描述的那样).当然,这种方法存在明显的潜在危险,但只要呼叫者确保正确使用此功能,它仍然只是"潜在的".调用者不得允许此函数产生的"cast away const"效果传播.如果参数是常量,则接收器指针也应该声明为`const char*`. (5认同)
  • BTW这个问题的规范解决方案是在`size_t`类型中返回*offset*而不是指针.然后调用者负责使用偏移量(将其添加到指针或其他方式),调用者自然可以使这些类型以安全的方式匹配. (2认同)

AnT*_*AnT 14

将非const指针从非修改函数返回到const数据的实践实际上是在C语言中相当广泛使用的习惯用法.它并不总是漂亮,但它已经相当成熟.

这里的reationale很简单:strchr本身是一个非修改操作.然而,我们需要strchr常量字符串和非常量字符串的功能,这也会将输入的常量传播到输出的常量.C和C++都没有为这个概念提供任何优雅的支持,这意味着在两种语言中你都必须编写两个几乎相同的函数,以避免带有const正确性的任何风险.

在C++中,您可以通过声明具有相同名称的两个函数来使用函数重载

const char *strchr(const char *s, int c);
char *strchr(char *s, int c);
Run Code Online (Sandbox Code Playgroud)

在C中你没有函数重载,所以为了在这种情况下完全强制const正确,你必须提供两个不同名称的函数,比如

const char *strchr_c(const char *s, int c);
char *strchr(char *s, int c);
Run Code Online (Sandbox Code Playgroud)

虽然在某些情况下这可能是正确的做法,但通常(并且正确地)认为它太麻烦并且涉及C标准.您可以通过仅实现一个函数以更紧凑(尽管风险更大)的方式解决这种情况

char *strchr(const char *s, int c);
Run Code Online (Sandbox Code Playgroud)

它将非const指针返回到输入字符串中(通过在出口处使用强制转换,就像你做的那样).请注意,此方法不违反任何语言规则,但它为调用者提供了违反它们的方法.通过抛弃数据的常量,这种方法简单地将责任从函数本身委托给调用者.只要调用者知道发生了什么并记得"玩得很好",即使用const限定指针指向const数据,由此类函数创建的const-correctness中的任何临时破坏都会立即得到修复.

我认为这个技巧是减少不必要的代码重复的完全可接受的方法(特别是在没有函数重载的情况下).标准库使用它.假设你明白自己在做什么,你也没有理由避免它.

现在,至于你的实现strchr,从风格的角度来看,我看起来很怪异.我会使用循环标头迭代我们正在操作的整个范围(完整字符串),并使用内部if来捕获提前终止条件

for (; *s != '\0'; ++s)
  if (*s == c)
    return (char *) s;

return NULL;
Run Code Online (Sandbox Code Playgroud)

但这样的事情总是个人喜好的问题.有人可能更喜欢

for (; *s != '\0' && *s != c; ++s)
  ;

return *s == c ? (char *) s : NULL;
Run Code Online (Sandbox Code Playgroud)

有人可能会说在函数s内修改函数parameter()是一种不好的做法.