c char指针问题

时间:2010-06-22 06:00:01

标签: c

如果我们声明char * p="hello";然后因为它是在数据部分写的,我们不能修改p点的内容,但我们可以修改指针本身。但我在C陷阱和陷阱中找到了这个例子 安德鲁柯尼希 AT& T贝尔实验室 默里山,新泽西州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

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

11 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:4)

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

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

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

x86是von Neumann architecture(而不是Harvard),所以基本级别的'数据'和'程序'内存之间没有明显的区别(即编译器不是强制为程序与数据存储器设置不同的类型,因此必然将任何变量限制为一个或另一个。)

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

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

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

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

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

答案 4 :(得分:1)

在古代,当K& K描述的时候。 R在他们的书“C程序设计语言”中是一个“标准”,你所描述的完全没问题。事实上,一些编译器跳过了箍,使字符串文字可写。在初始化时,他们会费力地将字符串从文本段复制到数据段。

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

答案 5 :(得分:1)

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

你永远不应该尝试修改保存字符串字符的内存,因为 可能 是只读的

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

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

答案 8 :(得分:0)

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

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

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

答案 9 :(得分:0)

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

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

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

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

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

     

答:字符串文字(正式术语   对于C中的双引号字符串   来源)可以稍微使用两个   不同的方式:

     
      
  1. 作为char数组的初始值设定项,如char的声明   a [],它指定初始值   该数组中的字符(和,   如果有必要,它的大小)。
  2.   
  3. 在其他地方,它变成一个未命名的静态字符数组,   并且可以存储该未命名的数组   在只读内存中,以及哪些内存   因此不一定如此   改性。在表达式上下文中,   数组一次转换为a   指针,像往常一样(见第6节),所以   第二个声明初始化p   指向未命名的数组的第一个   元件。
  4.         

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

答案 10 :(得分:0)

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

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

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

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

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