【问题标题】:C's strtok() and read only string literalsC 的 strtok() 和只读字符串文字
【发布时间】:2010-09-21 08:01:15
【问题描述】:

char *strtok(c​​har *s1, const char *s2)

对该函数的重复调用将字符串 s1 分解为“令牌”——即 字符串被分成子字符串, 每个都以 '\0' 结尾,其中 '\0' 替换任何字符 包含在字符串 s2 中。第一次通话 使用要标记为 s1 的字符串; 随后的调用使用 NULL 作为第一个 争论。指向开头的指针 当前令牌的返回;空值 如果没有更多则返回 令牌。

你好,

我刚才一直在尝试使用strtok,发现如果我将char* 传递给s1,则会出现分段错误。如果我传入char[]strtok 工作正常。

这是为什么?

我搜索了一下,原因似乎是char* 是只读的而char[] 是可写的。更详尽的解释将不胜感激。

【问题讨论】:

  • 因此,在 char* 版本中,指针指向只读内存。在 char[] 版本中,数组变量在读写内存中,C 启动中的初始化代码将字符串字面量复制到数组中。

标签: c string strtok


【解决方案1】:

您将char * 初始化为什么?

如果是这样的

char *text = "foobar";

那么你有一个指向一些只读字符的指针

对于

char text[7] = "foobar";

那么你就有了一个七元素的字符数组,你可以用它做你喜欢的事情。

strtok 写入您给它的字符串 - 用 null 覆盖分隔符并保留指向字符串其余部分的指针。

因此,如果您将只读字符串传递给它,它会尝试写入它,并且您会得到一个段错误。

另外,因为strtok 保留了对字符串其余部分的引用,它不能重入 - 您一次只能在一个字符串上使用它。最好避免,真的 - 请考虑 strsep(3) - 例如,请参见此处:http://www.rt.com/man/strsep.3.html(尽管它仍会写入字符串,因此具有相同的只读/段错误问题)

【讨论】:

  • 对不起,这听起来很愚蠢,但是是什么阻止我们在 char *text = "foobar"; 中说 *(text+3)= 'a'版本?
  • 我刚试过。除了段错误,没有什么能阻止你这样做,因为“text + 3”仍然指的是只读内存。
  • @Paul:strsep 是 strtok 的糟糕替代品,它存在许多与 strtok 相同的问题,即如果修改了字符串并且不适用于字符串文字。
  • 罗伯特,是的,strsep 也很穷。建议的替代方案?
  • @Paul:一个简单的解决方案是首先制作字符串的副本,以确保您拥有可修改的字符串版本并使用它。
【解决方案2】:

推断但未明确说明的重要一点:

根据您的问题,我猜您对 C 编程相当陌生,所以我想多解释一下您的情况。如果我弄错了,请原谅我; C 可能很难学习,主要是因为对底层机制存在细微的误解,所以我喜欢让事情尽可能简单。

如您所知,当您编写 C 程序时,编译器会根据语法为您预先创建所有内容。当您在代码中的任何位置声明变量时,例如:

int x = 0;

编译器读取这行文本并对自己说:好的,我需要将当前代码范围x 中的所有匹配项替换为对我分配用于保存整数的内存区域的常量引用。

当你的程序运行时,这一行会导致一个新的动作:我需要将x引用的内存区域设置为int0

注意这里的细微差别:参考点x 持有的内存位置是恒定的(并且不能更改)。但是,x 点的值可以更改。您通过分配在代码中执行此操作,例如x = 15;。另请注意,单行代码实际上相当于编译器的两个单独命令。

当你有这样的陈述时:

char *name = "Tom";

编译器的过程是这样的:好的,我需要将当前代码范围内的所有 name 替换为对我分配用于保存 char 指针值的内存区域的常量引用。它确实如此。

但是还有第二步,这相当于:我需要创建一个包含值“T”、“o”、“m”和NULL 的常量字符数组。然后我需要将代码中"Tom" 的部分替换为该常量字符串的内存地址。

当您的程序运行时,最后一步发生:将指向char 的值(不是常量)的指针设置为自动创建的字符串的内存地址(常数)。

所以char * 不是只读的。只有const char * 是只读的。但是在这种情况下,您的问题不是 char *s 是只读的,而是您的指针引用了只读的内存区域。

我之所以提出这一切,是因为理解这个问题是您从库中查看该函数的定义和自己理解该问题与不得不问我们之间的障碍。为了让这个问题更容易理解,我已经稍微简化了一些细节。

我希望这会有所帮助。 ;)

【讨论】:

  • NULL(空指针)不同于 NUL(ASCII 0)。这种情况很令人困惑,但由于 C 宏是 NULL 和 2 个 L,因此最好(在我看来)将 ASCII 0 称为 NUL(或“空字符”)。
【解决方案3】:

我责怪 C 标准。

char *s = "abc";

可以被定义为给出与

相同的错误
const char *cs = "abc";
char *s = cs;

基于字符串文字是不可修改的。但它不是,它被定义为编译。去搞清楚。 [编辑:Mike B 已经想通了——K&R C 中根本不存在“const”。ISO C 以及此后的每个版本的 C 和 C++,都希望向后兼容。所以它必须是有效的。]

如果它被定义为给出错误,那么你就不可能达到段错误,因为 strtok 的第一个参数是 char*,所以编译器会阻止你传入从文字生成的指针。

可能有趣的是,C++ 中曾有过弃用此功能的计划 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1996/N0896.asc)。但 12 年后,我无法说服 gcc 或 g++ 给我任何警告,让我将文字分配给非常量 char*,所以它并没有被大声反对。

[编辑:啊哈:-Wwrite-strings,不包含在 -Wall 或 -Wextra 中]

【讨论】:

  • const 关键字不在 K&R C 中。如果现有的 char* s = "abc"; 行数百万(数十亿?)突然失效,肯定会减慢(如果不停止)ANSI/ISO 的采用C. 即使今天尝试改变它也会面临类似的反对(正如你所发现的那样)。
  • 这解释了历史原因,谢谢。我想我的抱怨是现代 C 和 C++ 编译器应该发出警告,最初可能处于非常高的警告级别。我不介意来自 K&R 时代代码的一些警告,但如果不鼓励新代码这样做就好了。
【解决方案4】:

简而言之:

char *s = "HAPPY DAY";
printf("\n %s ", s);

s = "NEW YEAR"; /* Valid */
printf("\n %s ", s);

s[0] = 'c'; /* Invalid */

【讨论】:

    【解决方案5】:

    如果您查看您的编译器文档,您很可能可以设置一个选项来使这些字符串可写。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多