strchr实现如何工作

时间:2013-01-16 20:59:02

标签: c pointers const strchr

我尝试编写自己的strchr()方法实现。

现在看起来像这样:

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

最后一行是

return s;

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

4 个答案:

答案 0 :(得分:19)

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

以下是C标准所说的内容:

char *strchr(const char *s, int c);
  

strchr 函数定位第一次出现的 c   (转换为字符)在 s 指向的字符串中。该   终止空字符被认为是字符串的一部分。

这意味着这个程序:

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

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

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

问题是strchr()采用const char*参数,这意味着它承诺不会修改s指向的数据 - 但它会返回普通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;
}

我认为,(回答您的问题),strchr()的C实现必须将其结果转换为const char*转换为char*,或者执行相同的操作

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

const char * strchr ( const char * str, int character );
      char * strchr (       char * str, int character );

当然C不能这样做。

替代方法是将strchr替换为两个函数,一个使用const char*并返回const char*,另一个使用char*并返回{{} 1}}。与C ++不同,这两个函数必须具有不同的名称,可能是char*strchr

(从历史上看,strcchr已经定义const后添加到了strchr()。这可能是在不破坏现有代码的情况下保留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);

(全部在<string.h>中声明)和:

void *bsearch(const void *key, const void *base,
    size_t nmemb, size_t size,
    int (*compar)(const void *, const void *));

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

答案 1 :(得分:14)

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

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

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

const char *strchr(const char *s, int c);
char *strchr(char *s, int c);

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

const char *strchr_c(const char *s, int c);
char *strchr(char *s, int c);

虽然在某些情况下这可能是正确的做法,但它通常(并且正确地)被认为过于繁琐且涉及C标准。您可以通过仅实现一个函数

以更紧凑(尽管风险更大)的方式解决这种情况
char *strchr(const char *s, int c);

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

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

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

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

return NULL;

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

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

return *s == c ? (char *) s : NULL;

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

答案 2 :(得分:1)

const关键字表示无法修改参数。

您无法直接返回s,因为s被声明为const char *s,并且该函数的返回类型为char *。如果编译器允许您这样做,则可以覆盖const限制。

char*添加一个显式强制转换告诉编译器你知道自己在做什么(尽管Eric解释说,如果你不这样做会更好。)

更新:为了上下文,我引用了Eric的答案,因为他似乎删除了它:

  

你不应该修改s,因为它是一个const char *。

     

相反,定义一个表示char *类型结果的局部变量,并用它代替方法体中的s。

答案 3 :(得分:0)

函数返回值应该是字符的常量指针:

strchr接受const char*并且还应返回const char*。你返回一个非常量,这是有潜在危险的,因为返回值指向输入字符数组(调用者可能期望常量参数保持不变,但如果它的任何部分返回为{{1}它是可修改的指针)。

如果找不到匹配的字符,则函数返回值应为NULL:

如果找不到所寻找的角色,char *也应返回strchr。如果在找不到字符时返回非NULL,或者在这种情况下返回s,则调用者(如果他认为行为与strchr相同) 可能会假设结果中的第一个字符实际匹配(没有NULL返回值 没有办法判断是否有匹配。)

(我不确定这是不是你打算做的。)

以下是执行此操作的功能示例:

我写了这个函数并运行了几个测试;我添加了一些非常明显的健全性检查以避免潜在的崩溃:

NULL