从历史上看(也许通过重写部分内容),情况恰恰相反。在1970年代初期运行原型原型C(也许是BCPL)的第一台计算机(也许是PDP-11)上,没有MMU和内存保护(在大多数较旧的IBM / 360大型机上都存在)。这样的存储器的每个字节(包括处理的文字串或机器代码)可能是由错误的程序被覆盖(想象一个程序改变某些到在的printf(3)格式的字符串)。因此,文字字符串和常量是可写的。%
/
1975年少年时,我在1960年代的旧计算机上在巴黎的Palacous de laDécouverte博物馆编码,但没有内存保护:IBM / 1620只有一个核心内存-您可以通过键盘进行初始化,因此您必须键入几十个读取穿孔纸带上的初始程序的位数;CAB / 500具有磁鼓存储器;您可以通过鼓附近的机械开关禁用写一些音轨。
后来,计算机获得了某种形式的具有某些内存保护功能的内存管理单元(MMU)。有一个设备禁止CPU覆盖某种内存。因此,某些内存段,特别是代码段(又名代码段.text
)变为只读的(操作系统从磁盘加载它们的除外)。编译器和链接器很自然地将文字字符串放入该代码段中,并且文字字符串变为只读。当您的程序尝试覆盖它们时,这很糟糕,这是未定义的行为。而且,在虚拟内存中具有只读代码段具有显着的优势:运行同一程序的多个进程共享同一RAM(物理内存)。页)的代码段(在Linux上,请参见mmap(2)的MAP_SHARED
标志)。
如今,廉价的微控制器具有一些只读存储器(例如其Flash或ROM),并将其代码(以及文字字符串和其他常量)保留在那里。真正的微处理器(如平板电脑,笔记本电脑或台式机中的微处理器)具有复杂的内存管理单元和用于虚拟内存和页面调度的缓存机制。因此,可执行程序的代码段(例如,在ELF中)被映射为只读,可共享和可执行的段(在Linux上是通过mmap(2)或execve(2)进行存储的;顺便说一句,您可以将指令提供给ld)如果您确实想获得一个可写的代码段)。写或滥用它通常是分段错误。
因此,C标准是巴洛克式的:从法律上讲(仅出于历史原因),文字字符串不是const char[]
数组,而只是char[]
禁止覆盖的数组。
顺便说一句,当前很少有语言允许覆盖字符串文字(即使是Ocaml,从历史上也很糟糕的是,可写文字字符串最近在4.02中就改变了这种行为,现在具有只读字符串)。
当前的C编译器能够优化并拥有"ions"
并"expressions"
共享其最后5个字节(包括终止的空字节)。
尝试编译文件C代码foo.c
与gcc -O -fverbose-asm -S foo.c
生成的汇编文件中,并期待foo.s
通过GCC
最后,C 的语义足够复杂(了解有关试图捕获它的CompCert和Frama-C的更多信息),并且添加可写的常量文字字符串将使其变得更加神秘,同时使程序更弱,甚至更不安全(并且花费更少的时间)定义的行为),因此将来的C标准不太可能接受可写的文字字符串。也许恰恰相反,它们会使它们const char[]
像道德上那样排列整齐。
还请注意,由于多种原因,可变数据比常量数据更难由计算机处理(缓存一致性),难以编码(供开发人员理解)。因此,最好让您的大多数数据(尤其是文字字符串)保持不变。阅读有关函数式编程 范例的更多信息。
在IBM / 7094上的Fortran77旧版本中,错误甚至可以更改常量:如果您CALL FOO(1)
并且如果FOO
碰巧修改了通过引用2传递的参数,则实现可能会将其他情况从1更改为2,这确实是一个错误。顽皮的bug,很难找到。