C语言中的字符串:陷阱与技巧

10

下个月我将指导一支ACM团队(天知道),现在是讨论C语言中字符串的时候了。除了讨论标准库,strcpystrcmp等,我想给他们一些提示(例如str[0]等同于*str,以及类似的技巧)。

你知道有没有任何列表(比如备忘单)或者你自己的经验可以分享吗?

我已经知道了ACM竞赛的书籍(非常好,特别是这个),但我想要一些实用技巧。

谢谢。

编辑:非常感谢大家。我将接受得票最高的答案,并已经投票支持其他我认为相关的答案。我希望尽快在这里做一个总结(就像我在这里做的那样)。我现在有足够的材料,确信这将极大地改善关于字符串的课程。再次感谢。


*str不等同于str[0]。所以,从这里开始。 - jkeys
6
@Hooked: 怎么不等同呢?a[i] 相当于 *(a+i),也就是说 a[0] 相当于 *(a+0),而 *(a+0) 又等同于 *a - Chuck
3
我的Google搜索未能找到与C语言相关的“方向引用”短语的任何用法。我之前解释过的C语言中数组和指针的等价性是相当知名的,甚至在维基百科页面上都有说明,所以我真的搞不清楚你想要表达什么。 - Chuck
3
@Hooked,您的评论事实上是错误的。您能删除它吗,这样就不会使其他人感到困惑了吗? - Dervin Thunk
1
a 的第一个元素和 a 指向的元素有什么不同吗?否则,你刚才说它们是相同的。 - Chuck
显示剩余4条评论
16个回答

26

显而易见,但我认为知道字符串只是由一个零字节分隔的字节数组很重要。正如您可能知道的那样,C字符串并不是非常用户友好的。

  • Writing a zero byte somewhere in the string will truncate it.
  • Going out of bounds generally ends bad.
  • Never, ever use strcpy, strcmp, strcat, etc.., instead use their safe variants: strncmp, strncat, strndup,...
  • Avoid strncpy. strncpy will not always zero delimit your string! If the source string doesn't fit in the destination buffer it truncates the string but it won't write a nul byte at the end of the buffer. Also, even if the source buffer is a lot smaller than the destination, strncpy will still overwrite the whole buffer with zeroes. I personally use strlcpy.
  • Don't use printf(string), instead use printf("%s", string). Try thinking of the consequences if the user puts a %d in the string.
  • You can't compare strings with
    if( s1 == s2 )
                doStuff(s1);
    You have to compare every character in the string. Use strcmp or better strncmp.
    if( strncmp( s1, s2, BUFFER_SIZE ) == 0 )
             doStuff(s1);

4
如果你确实在使用printf而不是进行其他操作的包装宏,那么puts和fputs是你正在寻找的函数。 - user14554
strlcpy()是标准C吗?这对于比赛来说可能很重要。如果不是,就要准备写它。此外,如果你能证明目标足够长,strcpy等函数也是安全的。 - David Thornley
我个人喜欢使用strncpy,紧跟着在目标数组末尾写入一个NUL。这样我就知道它没有被覆盖,并且我知道这个字符串已经结束了。由于我不认为strlcpy是标准的(据我所知),当我在不同环境之间切换时,我不想依赖它。 - Michael Kohne
1
@David Thorley:strlcpy确实不是标准函数,而那个白痴Drepper拒绝将其放入glibc。但事实证明它真的很棒,因为我编写的strlcpy比strcpy更快。我不喜欢strncpy,因为它会覆盖整个数组,而不仅仅是我给出的大小。 - Kasper
请注意,除非您可以安全地使用memmove()memcpy(),否则不能安全地使用strncat()。特别是,除非您知道*target == '\0',否则strncat(target, source, sizeof(target))是不正确的。通常使用strncat()是一个错误。 - Jonathan Leffler

5

滥用strlen()会严重影响性能。

for( int i = 0; i < strlen( string ); i++ ) {
    processChar( string[i] );
}

如果不加优化,时间复杂度至少为O(n2)。

int length = strlen( string );
for( int i = 0; i < length; i++ ) {
    processChar( string[i] );
}

该算法的时间复杂度至少为O(n)。对于那些没有花时间思考的人来说,这并不是那么显而易见。


但是编译器不会优化它并且只实际访问 strlen() 函数一次吗? - galois
1
@jaska 可能会,也可能不会 - 这取决于许多因素。标准确实不要求优化掉它,也不禁止这种优化。 - sharptooth

3
以下函数可用于实现非变异的 strtok
strcspn(string, delimiters)
strspn(string, delimiters)

第一个函数在你传入的分隔符集合中找到第一个字符。第二个函数在你传入的非分隔符集合中找到第一个字符。

我更喜欢这些函数而不是 strpbrk,因为如果它们无法匹配,它们会返回字符串的长度。


3

str[0] 相当于 0[str],或者更一般地说,str[i]i[str],而 i[str]*(str + i)

NB

这不仅适用于字符串,也适用于 C 数组。


5
我并不认为这非常重要。 - GManNickG
1
3["hello"] 等价于 "hello"[3] 这样的事情,虽然是真的,但实际上只是一些古怪的琐事,没有人会使用。 - Adam Rosenfield
1
这全是因为加法是可交换的。x[y] 是 *(x + y),而 y[x] 是 *(y + x)。 - smcameron

3

标准库中的 strn* 变体并不一定会将目标字符串以 null 结尾。

例如:MSDN 在 strncpy 的文档中如下描述:

strncpy 函数将 strSource 的前 count 个字符复制到 strDest 中,并返回 strDest。如果 count 小于或等于 strSource 的长度,则不会自动将 null 字符附加到已复制的字符串中。 如果 count 大于 strSource 的长度,则会使用 null 字符填充目标字符串,直到其长度为 count。


2
实际上,这并不是完整的strn*系列,只有strncpy。 strncat也有自己的问题。然而,写入null并不能使您的程序更安全。如果您想传输文件/ etc / passwd-archive / public-data的内容,但是您的数据被strncpy截断为/ etc / passwd,该怎么办? - Kasper
是的,在非托管动态内存环境中安全使用字符串的一般问题本身就是一个硕士论文。假设你仍然想要做到这一点 :) - MSN

2

strtok 不是线程安全的,因为它使用一个可变的私有缓冲区在调用之间存储数据;您也不能交错或嵌套 strtok 调用。

一个更有用的替代方法是 strtok_r,尽可能使用它。


1
strtok是一个来自地狱的函数。使用逗号作为分隔符从字符串 "asdf",,,,"fdsa" 中提取,只能得到2个结果,而不是5个。 - EvilTeach
strtok_r() 可能在比赛中不可用。但是如果可以的话,请避免使用 strtok()。 - David Thornley

2

在使用字符串时,不要混淆strlen()sizeof()

char *p = "hello!!";
strlen(p) != sizeof(p)

sizeof(p) 在编译时返回指针的大小(4或8字节),而strlen(p) 在运行时计算以null结尾的字符数组的长度(在本例中为7)。


2

kmm已经有一个很好的列表。以下是我在开始编写C代码时遇到的问题:

  1. 字符串字面量有自己的内存部分,并且始终可访问。因此,它们可以是函数的返回值。

  2. 字符串的内存管理,尤其是使用高级库(而不是libc)。如果字符串由函数返回或传递给函数,谁负责释放它?

  3. 何时应使用“const char *”,何时应使用“char *”。如果函数返回“const char *”,这告诉我什么。

所有这些问题并不太难学习,但如果没有人教你,很难弄清楚。


请记住,字符串字面值是const char *类型的,如果尝试更改它们,则行为是未定义的。 - David Thornley

1

我发现char buff[0]技术非常有用。考虑以下内容:

struct foo {
   int x;
   char * payload;
};

对比

struct foo {
   int x;
   char payload[0];
};

请查看https://dev59.com/tXVC5IYBdhLWcg3wcgmd

有关影响和变化,请参见链接。


1
我想指出过度依赖内置字符串函数的性能陷阱。
char* triple(char* source)
{
   int n=strlen(source);
   char* dest=malloc(n*3+1);
   strcpy(dest,src);
   strcat(dest,src);
   strcat(dest,src);
   return dest;
 }

还有过早优化的陷阱吗? :-) - Andrew Y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接