c char指针问题

如果我们声明char * p="hello"; 那么因为它是在数据部分写的,所以我们不能修改p点的内容,但我们可以修改指针本身。 但是我在C陷阱和陷阱中发现了这个例子Andrew Koenig AT&T贝尔实验室Murray Hill,新泽西州07974

这个例子是

 char *p, *q; p = "xyz"; q = p; q[1] = 'Y'; 

q将指向包含字符串xYz的内存。 p也是如此,因为p和q指向相同的内存。

如果我提到的第一个语句也是真的,那怎么回事?同样我运行了下面的代码

 main() { char *p="hai friends",*p1; p1=p; while(*p!='\0') ++*p++; printf("%s %s",p,p1); } 

并得到输出为ibj!gsjfoet

请解释在这两种情况下我们如何修改内容? 提前致谢

您的相同示例会导致系统出现分段错误。

你在这里遇到了未定义的行为。 .data (注意字符串文字也可能在.text )不一定是不可变的 – 不能保证机器会写保护该内存(通过页表),具体取决于操作系统和编译器。

只有您的操作系统可以保证数据部分中的内容是只读的,甚至包括设置段限制和访问标志以及使用远指针等,因此并不总是这样做。

C本身没有这样的限制; 在平面内存模型中(几乎所有32位操作系统最近都使用它),地址空间中的任何字节都可能是可写的,甚至是代码部分中的内容。 如果你有一个指向main()的指针,以及一些机器语言知识,以及操作系统设置恰到好处(或者更确切地说,无法阻止它),你可能会将其重写为只返回0.注意这个是一种黑魔法,并且很少有意识地完成,但它是使C成为系统编程的强大语言的一部分。

即使你可以做到这一点,似乎没有错误,这是一个坏主意。 根据所讨论的程序,最终可能会使缓冲区溢出攻击变得非常容易。 一篇解释这个的好文章是:

https://www.securecoding.cert.org/confluence/display/seccode/STR30-C.+Do+not+attempt+to+modify+string+literals

它取决于编译器是否有效。

x86是冯·诺依曼架构 (与哈佛相反),因此基本级别的“数据”和“程序”内存之间没有明显的区别(即编译器不会强制为程序与数据存储器使用不同的类型,所以不一定会将任何变量限制在一个或另一个)。

因此,一个编译器可能允许修改字符串而另一个编译器不允许。

我的猜测是,一个更宽松的编译器(例如cl,MS Visual Studio C ++编译器)会允许这样做,而更严格的编译器(例如gcc)则不会。 如果您的编译器允许它,那么它可能会有效地将您的代码更改为:

 ... char p[] = "hai friends"; char *p1 = p; ... // (some disassembly required to really see what it's done though) 

也许是出于“良好意图”允许新的C / C ++编码器以较少的限制/较少的混淆错误进行编码。 (这是一个’好事’是否有很多争论,我将主要从这篇文章中保留我的观点:P)

出于兴趣,你使用了什么编译器?

在过去,当K&R在他们的书“The C Programming Language”中描述的C是讽刺“标准”时,你所描述的完全没问题。 事实上,一些编译器跳过了箍,使字符串文字可写。 他们在初始化时费力地将字符串从文本段复制到数据段。

即使是现在,gcc还是有一个标志来恢复这种行为: -fwritable-strings

 main() { int i = 0; char *p= "hai friends", *p1; p1 = p; while(*(p + i) != '\0') { *(p + i); i++; } printf("%s %s", p, p1); return 0; } 

这段代码会给出输出:hai friends hai friends

修改字符串文字是一个坏主意,但这并不意味着它可能无法正常工作。

一个非常好的理由不允许:允许您的编译器获取相同字符串文字的多个实例,并使它们指向同一块内存。 因此,如果在代码中的其他位置定义了“xyz”,则可能会无意中破坏其他期望它保持不变的代码。

您的程序也适用于我的系统(windows + cygwin)。 但是标准说你不应该这样做,尽管结果没有定义。

以下摘录自C:A参考手册5 / E,第33页,

您永远不应该尝试修改包含字符串字符常量的内存,因为它可能是只读的

 char p1[] = "Always writable"; char *p2 = "Possibly not writable"; const char p3[] = "Never writable"; 

p1线将始终有效; p2行可能有效或可能导致运行时错误 ; p3将始终导致编译时错误。

虽然在您的系统上修改字符串文字是可能的,但这是您平台的一个怪癖,而不是语言的保证。 实际的C语言对.data部分或.text部分一无所知。 这就是所有实施细节。

在某些嵌入式系统上,您甚至没有文件系统来包含带有.text部分的文件。 在某些此类系统上,您的字符串文字将存储在ROM中,并且尝试写入ROM只会使设备崩溃。

如果您编写的代码依赖于未定义的行为,并且只能在您的平台上运行,那么您可以保证迟早会有人认为将它移植到一些无法按预期工作的新设备上是个好主意。 。 当这种情况发生时,一群愤怒的嵌入式开发人员会追捕你并刺伤你。

p实际上指向只读内存。 分配给数组p的结果可能是未定义的行为。 仅仅因为编译器让你逃脱它并不意味着它没关系。

从C-FAQ中查看这个问题: comp.lang.c FAQ列表·问题1.32

问:这些初始化之间有什么区别?

 char a[] = "string literal"; char *p = "string literal"; 

如果我尝试为p [i]分配新值,我的程序会崩溃。

答:字符串文字(C源中双引号字符串的正式术语)可以两种略有不同的方式使用:

  1. 作为char数组的初始化器,如char a []的声明,它指定该数组中字符的初始值(如果需要,还指定其大小)。
  2. 在其他任何地方,它变成一个未命名的静态字符数组,这个未命名的数组可能存储在只读存储器中,因此不一定能被修改。 在表达式上下文中,像往常一样将数组一次转换为指针(参见第6节),因此第二个声明将p初始化为指向未命名数组的第一个元素。

有些编译器有一个开关控制字符串文字是否可写(用于编译旧代码),有些编译器可能有选项可以将字符串文字正式地视为const char数组(以便更好地捕获错误)。

我认为你在使用C,C ++或其他低级语言时理解一个非常重要的一般概念时会产生很大的混淆。 在低级语言中,有一个隐含的假设,而不是程序员知道他/她在做什么并且没有编程错误

这种假设允许语言的实现者忽略如果程序员违反规则会发生什么。 最终结果是在C或C ++中没有“运行时错误”保证……如果你做了坏事只是它没有定义 (“未定义的行为”是法律术语)会发生什么。 可能是一次崩溃(如果你非常幸运的话),或者可能只是显然没什么(不幸的是大部分时间……可能是一个完全有效的地方崩溃,后来有一百万个执行指令)。

例如,如果你在一个数组之外访问可能是你会崩溃,可能不是,甚至可能是一个守护进程会从你的鼻子出来(这是你可能在互联网上找到的“鼻守护进程”)。 编写编译器的人并不是在考虑这个问题。

永远不要那样做(如果你关心编写体面的程序)。

使用低级语言的人的另一个负担是你必须非常好地学习所有规则,而且绝不能违反它们。 如果您违反规则,您不能指望“运行时错误天使”来帮助您……只有“未定义的行为守护程序”存在于那里。