【问题标题】:How would one modify a constant string?如何修改一个常量字符串?
【发布时间】:2019-09-06 21:58:09
【问题描述】:

要创建一个我可以修改的字符串,我可以这样做:

// Creates a variable string via array
char string2[] = "Hello";
string2[0] = 'a'; // this is ok

并创建一个不能修改的常量字符串:

// Creates a constant string via a pointer
char *string1 = "Hello";
string1[0] = 'a'; // This will give a bus error

那么我的问题是如何修改常量字符串(例如,通过强制转换)?而且,这被认为是不好的做法,还是在 C 编程中通常会这样做?

【问题讨论】:

  • 如果我没记错的话,初始化为“Hello”的string2const。如果通过strcpystrdup 初始化,肯定不是const
  • @john 你错了。 string2 不是常量。
  • 共享,你不能做你提议的事。字符串文字是不可变的。
  • @john: char string2[] = "Hello"; 通过将"Hello" 的内容复制到string2 来初始化string2(在C 计算模型中)。这不会使string2const
  • 呃。我想我的意思是"Hello""Hello"const,不是吗?

标签: c


【解决方案1】:

根据定义,您不能修改常量。如果您想获得相同的效果,请制作常量的非常量副本并对其进行修改。

【讨论】:

  • 感谢您。您能否展示一个代码示例来说明make a non-constant copy of the constant and modify that 的含义?
  • 可以像string1 = strdup(string1); string1[0] = 'a';一样简单。完成后别忘了free
  • 或者如果您不想使用动态内存,并且不介意固定大小的缓冲区和溢出的可能性,只需char string2[20]; strcpy(string2, string1); string2[0] = 'a';
  • @SteveSummit 那里最好用strncpy
  • @JL2210 不是。如果您不担心缓冲区溢出,strcpy 很好。如果您担心缓冲区溢出,您真的想使用其他东西。 strncpy 基本没用。 (是的,你可以使用它,但是太麻烦了。)
【解决方案2】:

如何修改一个常量字符串(例如,通过强制转换)?

如果你的意思是,一个人尝试如何修改它,你甚至不需要演员表。您的示例代码是:

char *string1 = "Hello";
string1[0] = 'a';         // This will give a bus error

如果我编译并运行它,我会得到一个总线错误,正如预期的那样,就像你做的那样。但是如果我使用-fwritable-strings 编译,这会导致编译器将字符串常量放入读/写内存中,它就可以正常工作。

我怀疑你在考虑一个稍微不同的案例。如果你写

const char *string1 = "Hello";
string1[0] = 'a';         // This will give a compilation error

情况发生了变化:您甚至无法编译代码。您不会在运行时收到总线错误,而是在编译时收到类似于“只读变量不可分配”的致命错误。

以这种方式编写代码后,可以尝试通过显式转换绕过const-ness:

((char *)string1)[0] = 'a';

现在代码编译了,我们又回到了总线错误。 (或者,使用-fwritable-strings,它会再次起作用。)

这被认为是不好的做法,还是 C 编程中通常会做的事情

我会说这被认为是不好的做法,这不是通常会做的事情。

不过,我仍然不确定您要问什么,或者我是否已经回答了您的问题。这方面经常存在混淆,因为我们通常担心两种不同的“恒定性”:

  1. 对象是否存储在只读内存中

  2. 由于程序架构的限制,是否不应该修改变量

其中第一个是由操作系统和 MMU 硬件强制执行的。无论您使用或未使用哪种编程语言结构都无关紧要——如果您尝试写入只读位置,它就会失败。

其中的第二个与软件工程和编程风格有关。如果一段代码承诺不会修改某些内容,那么该承诺可能会让您对程序的其余部分做出有用的保证。例如,strlen 函数承诺不会修改你交给它的字符串;它所做的只是检查字符串以计算其长度。

令人困惑的是,至少在 C 中,const 关键字主要与第二类有关。当您将某些内容声明为const 时,它不一定(实际上通常不会)导致编译器将某些内容放入只读内存中。它所做的只是让编译器在你违背承诺时给你警告和错误——如果你不小心试图修改你在其他地方声明为const 的东西。 (而且因为它是编译时的事情,你也可以很容易地“作弊”并通过强制转换来关闭这种常量。)

但是只读内存,而现在,编译器通常会在其中放置字符串常量,尽管(同样令人困惑,但出于历史原因)字符串常量 em> 在 C 中具有 const char [] 类型。但由于只读内存是硬件,因此不能通过强制转换“关闭”它。

【讨论】:

  • 我认为字符串文字的不变性与某种只读硬件无关。我认为这与简单地放在不应该在程序执行期间写入的内存位置中的值有关。例如,ELF 的 .rodata 部分。
  • 总线错误?我预计会出现分段错误。我还认为 -fwritable-strings 已从 GCC 中删除。
  • @ChristianGibbons 我的措辞有点含糊,但关键是它通常是 MMU 硬件实际检测到非法写入。但你是对的,它不是硬件 ROM。
  • @JL2210 我什至不知道会发生什么。 “总线错误”是 OP 报告的,是的,它也是(特别是:“总线错误:10”)我得到的。
【解决方案3】:

您不能在 C 中以安全或可靠的方式修改字符串文字的内容;它会导致未定义的行为。来自关于字符串文字的 C11 标准草案第 6.4.5 p7 节:

如果这些数组的元素具有 适当的值。 如果程序试图修改这样的数组,行为是 未定义。

【讨论】:

  • 有时您可以修改字符串文字。 C 标准只是说 it 没有定义行为。它并没有说你不能或需要一个实现来阻止你。人们应该理解这一点,以防万一他们遇到字符串文字被修改(不正确)的情况,并且他们需要弄清楚发生了什么以便调试程序。
  • 应该是:不能“安全”或“可靠”?
  • @EricPostpischil 如果我强调它不能合法完成会更好吗?
  • @ChristianGibbons:这并不违法; C 标准中没有任何内容禁止您尝试或禁止实现允许它——甚至支持它作为定义的扩展。它根本没有被标准定义。如果没有 C 实现的一些保证,说它不可移植或不可靠是正确的。
  • @EricPostpischil 你不认为未定义的行为是非法的吗?考虑到未定义的行为会使整个程序无效,我认为它是非法的。
【解决方案4】:

尝试修改常量字符串文字是undefined behavior。您可能会遇到总线错误,就像您的情况一样,或者程序甚至可能根本没有指示写入失败。这对您来说是未定义的行为 - 该语言在这一点上没有做出任何承诺。

您可以重新分配指针(丢失对字符串 "Hello" 的引用):

char *s1 = "Hello";
printf("%s ", s1);
s1 = "World";
printf("%s\n", s1);

【讨论】:

  • 有时您可以修改字符串文字。 C 标准只是说 it 没有定义行为。它并没有说你不能或需要一个实现来阻止你。人们应该理解这一点,以防万一他们遇到字符串文字被修改(不正确)的情况,并且他们需要弄清楚发生了什么以便调试程序。
  • 或者换句话说,“你可能会得到一个总线错误,或者程序甚至可能没有指示写入失败,或者写入可能成功。”曾几何时,字符串文字通常可修改的,即使在今天,在许多编译器下,如果您愿意,您仍然可以使用-fwritable-strings 请求这种遗留行为。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-03
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多