修改指针指向的字符串是否有效?
这是一个连接两个字符串的程序的简单示例。
#include void strcat(char *s, char *t); void strcat(char *s, char *t) { while (*s++ != '\0'); s--; while ((*s++ = *t++) != '\0'); } int main() { char *s = "hello"; strcat(s, " world"); while (*s != '\0') { putchar(*s++); } return 0; }
我想知道为什么它有效。 在main()中,我有一个指向字符串“hello”的指针。 根据K&R的书,修改这样的字符串是未定义的行为。 那么为什么该程序能够通过附加“世界”来修改它? 或者是否附加不被视为修改?
未定义的行为意味着编译器可以发出执行任何操作的代码。 工作是未定义的子集。
我给MSN +1了,但至于它为何起作用,那是因为还没有任何东西来填补你的弦后面的空间。 声明一些变量,增加一些复杂性,你会开始看到一些古怪的东西。
也许令人惊讶的是,您的编译器已将文字"hello"
分配给读/写初始化数据而不是只读初始化数据。 你的任务会破坏与该点相邻的任何东西,但是你的程序很小而且很简单,你看不到效果。 (把它放在for循环中,看看你是否在破坏" world"
文字。)
它在Ubuntu x64上失败,因为gcc
将字符串文字放在只读数据中,当你尝试编写时,硬件MMU对象。
你这次很幸运。
特别是在调试模式下,一些编译器会在声明周围放置备用内存(通常填充一些明显的值),这样你就可以找到这样的代码。
它还取决于指针的声明方式。 例如,可以更改ptr,以及ptr指向的内容:
char * ptr;
可以改变ptr指向的内容,但不能改变ptr:
char const * ptr;
可以改变ptr,但不是ptr指向的:
const char * ptr;
无法改变任何事情:
const char const * ptr;
根据C99规范(C99:TC3,6.4.5,§5),字符串文字是
[…]用于初始化静态存储持续时间和长度的数组,足以包含序列。 […]
这意味着它们具有char []
类型,即原则上可以进行修改。 为什么你不应该这样做在§6中解释:
如果这些数组的元素具有适当的值,则这些数组是否不同是未指定的。 如果程序试图修改此类数组,则行为未定义。
具有相同内容的不同字符串文字可以 – 但不必 – 映射到相同的内存位置。 由于行为未定义,编译器可以自由地将它们放在只读部分中,以便彻底失败,而不是引入可能难以检测的错误源。
我想知道为什么它有效
它没有。 它在Ubuntu x64上导致分段错误; 要使代码工作,它不应该只在您的机器上工作 。
将修改后的数据移动到堆栈中可以解决linux中的数据区域保护问题:
int main() { char b[] = "hello"; char c[] = " "; char *s = b; strcat(s, " world"); puts(b); puts(c); return 0; }
虽然你只是安全,因为“世界”适合堆栈数据之间未使用的空间 – 将b
更改为“hello to”并且linux检测到堆栈损坏:
*** stack smashing detected ***: bin/clobber terminated
编译器允许你修改s,因为你不正确地将它标记为非const – 指向静态字符串的指针应该是
const char *s = "hello";
如果缺少const修饰符,您基本上已禁用安全性,这会阻止您写入不应写入的内存。 C几乎没有让你在脚下射击自己。 在这种情况下,你很幸运,只有擦过你的小拇指。
s指向一些持有“hello”的内存,但并不打算包含更多内容。 这意味着您很可能会覆盖其他内容。 这是非常危险的,即使它似乎有效。
两点意见:
- * in * s–不是必需的。 s–就足够了,因为你只想减少价值。
- 你不需要自己编写strcat。 它已经存在(你可能知道,但无论如何我告诉你:-))。