在 C 中编辑不可变字符串 - 与旧编译器一起使用,但与现代编译器中断

Posted

技术标签:

【中文标题】在 C 中编辑不可变字符串 - 与旧编译器一起使用,但与现代编译器中断【英文标题】:Editing Immutable Strings in C- worked with older compiler but breaks with modern one 【发布时间】:2020-08-15 07:20:47 【问题描述】:

我正在处理一些在 Solaris 10 上运行的古老遗留代码。在旧服务器上,代码编译并运行没有问题。

然后将代码迁移到 Solaris 11 服务器,代码仍可在该服务器上编译,但在运行时会创建一个 seg 故障核心转储。

在这两种情况下,使用的编译器都是 /opt/SUNWspro/bin/cc。

这里是sn-p的代码:

#include <stdio.h>

char   *blank = "                                   ";

main(argc,argv)
int argc;
char **argv;


blank[35] = '\0';
printf("Success.\n");


这在 Solaris 10 上有效,但在 Solaris 11 上会导致分段错误(核心转储)。 通常我会说段错误是由于尝试写入空白[35]而导致空白[]数组仅上升到空白[34](它用35个空格字符初始化),除了此代码在Solaris上工作10.

另外,当我将行更改为 'blank[34] = '\0';'在新服务器上,我仍然得到一个段错误核心转储。

当我将空白更改为普通数组(并且还对主数组进行现代化改造)时,一切正常,正如我所期望的那样:

#include <stdio.h>

char blank[35];

int main(int argc,char **argv)


  int i;

  for (i=0; i<34; i++)
  
   blank[i] = ' ';
  

  blank[34] = '\0';
  printf("Success.\n");
  return 0;

我真正需要知道的是为什么这段代码在旧服务器上运行良好,我忽略了什么?我可以更改代码以使用普通数组使其在新服务器上运行,但这会导致什么样的问题?

【问题讨论】:

修改字符常量是否像未定义的行为? 在后来的 C 编译器中,像 " ... " 这样的文字字符串被认为是不可变的。它可能在 Solaris 10 的一个古老版本的 C 中被允许。(在括号外声明参数类型的语法可以追溯到白垩纪时期。) 关于; main(argc,argv) int argc; char **argv; 1) 现代编译器不会假定返回类型为int,因此必须明确写出int 2) 将参数定义放在函数签名之后,20 年前,大多数现代编译器仍然支持那个,但你应该把它写成:int main( int argc, char **argv) 关于; char *blank = " "; 这会将blank 指向的数组放入只读内存中,因此如果不引起段错误事件,则无法对其进行修改。 未定义的行为意味着它可以为所欲为,包括有时看似正常工作。 【参考方案1】:

他们是否使用完全相同版本的 C 编译器和完全相同的标志?

旧版本的 Studio 编译器 (/opt/SUNWspro/...) 默认将常量字符串放入可写内存中,除非您使用 -features=conststrings 标志将它们放入只读内存中。

更高版本的 Studio 编译器将 -features=conststrings 设为默认值,并要求 -features=no%conststrings 使其再次可写,如 https://docs.oracle.com/cd/E77782_01/html/E77788/bjapr.html#OSSCGbjaqo 的 Studio 12.6 文档中所示。

这类似于 gcc 对等效标志 -fwritable-strings 所做的事情,该标志在最旧的版本中默认开启,然后在 几个版本,在成为removed in gcc 4.0 之前,总是制作常量字符串 在只读存储器中。

【讨论】:

谢谢!使用-features=no%conststrings 标志重新编译代码使所有代码都能完美运行!这不仅解释了为什么代码过去可以工作而不再工作,而且提供了一个快速的解决方案——对我的 makefile 进行一次更改,现在所有代码都可以工作(这种不断变化的字符串文字废话发生在几个地方)。这就是我需要的答案。【参考方案2】:

请注意,您没有char blank[],而是char *blank,它指向一个字符串文字。字符串文字是不可变的。此代码试图修改 blank 指向的文字字符之一,导致未定义行为。未定义行为的有趣之处在于它可以做任何事情,包括按照您的预期方式运行。

还值得注意的是,字符串文字已经隐式地以空值结尾,因此无论如何都不需要在末尾显式添加 '\0'。

【讨论】:

我完全同意,这就是为什么这段代码成功运行的事实让我感到莫名其妙。我的问题是为什么这段代码可以工作,而不是为什么它不工作(因为它不应该工作)。 未定义行为(假设它在 K&R 时代是未定义的;我对 ANSI/ISO 标准非常熟悉)可能会导致任何结果,包括按您的意愿工作的代码。【参考方案3】:

很可能,问题不在于操作系统版本,而在于您构建代码时使用的编译器。这段代码有很多问题,很容易看出它在一个编译器上是如何工作的,而在另一个编译器上却不行。鉴于我们不知道您使用的是什么编译器,所以我不会推测。

但我敢打赌,如果机器具有相同的硬件架构(例如,两者都是 ultrasparc),那么如果你在 Solaris 10 上编译你的二进制文件,它在 Solaris 11 上运行得很好。

【讨论】:

以上是关于在 C 中编辑不可变字符串 - 与旧编译器一起使用,但与现代编译器中断的主要内容,如果未能解决你的问题,请参考以下文章

将新的 MS C++ 编译器与旧的 Visual Studio 一起使用

C 的哈希表实现

C++11 中的非类型可变参数函数模板

Java技术专题「String技术系列」带你一起探究字符串不可变的特性

在 C 中使用 ioctl() 设置不可变标志

可变参数省略号使用简介