C 修改指针指向的字符串是否有效?

C 修改指针指向的字符串是否有效?,c,C,下面是一个连接两个字符串的程序的简单示例 #include <stdio.h> 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"); w

下面是一个连接两个字符串的程序的简单示例

#include <stdio.h>

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;
}
#包括
无效strcat(char*s,char*t);
无效strcat(char*s,char*t){
而(*s++!='\0');
s--;
而((*s++=*t++)!='\0');
}
int main(){
char*s=“你好”;
strcat(s,“世界”);
而(*s!='\0'){
putchar(*s++);
}
返回0;
}

我想知道它为什么有效。在main()中,我有一个指向字符串“hello”的指针。根据K&R的书,修改这样的字符串是未定义的行为。那么为什么程序能够通过添加“world”来修改它呢?或者追加不被视为修改?

未定义的行为意味着编译器可以发出执行任何操作的代码。工作是未定义行为的子集。

未定义行为意味着编译器可以发出执行任何操作的代码。工作是未定义的子集。

这次你很幸运。

特别是在调试模式下,一些编译器会在声明周围放置备用内存(通常填充一些明显的值),这样您就可以找到这样的代码。

这次您很幸运。

特别是在调试模式下,一些编译器会在声明周围放置备用内存(通常填充一些明显的值),这样您就可以找到这样的代码。

编译器允许您修改s,因为您不正确地将其标记为非常量——指向这样的静态字符串的指针应该是

const char *s = "hello";

由于常量修饰符丢失,您基本上禁用了防止写入不应该写入的内存的安全性。C几乎不能阻止你射中自己的脚。在这种情况下,你很幸运,只擦伤了你的小拇指。

编译器允许你修改s,因为你没有正确地将它标记为非常量——指向这样一个静态字符串的指针应该是

const char *s = "hello";

由于常量修饰符丢失,您基本上禁用了防止写入不应该写入的内存的安全性。C几乎不能阻止你射中自己的脚。在这种情况下,您很幸运,只擦伤了小拇指。

这还取决于指针的声明方式。例如,可以更改ptr,以及ptr指向的内容:

char * ptr;
const char * ptr;
可以更改ptr指向的内容,但不能更改ptr:

char const * ptr;
可以更改ptr,但不能更改ptr所指的内容:

char * ptr;
const char * ptr;
无法更改任何内容:

const char const * ptr;

它还取决于指针的声明方式。例如,可以更改ptr,以及ptr指向的内容:

char * ptr;
const char * ptr;
可以更改ptr指向的内容,但不能更改ptr:

char const * ptr;
可以更改ptr,但不能更改ptr所指的内容:

char * ptr;
const char * ptr;
无法更改任何内容:

const char const * ptr;

I+1'd MSN,但至于它为什么有效,这是因为还没有出现任何东西来填充字符串后面的空间。声明更多的变量,增加一些复杂性,你会开始看到一些古怪的东西。

I+1'd MSN,但至于它为什么会起作用,那是因为还没有出现任何东西来填充字符串后面的空间。声明更多的变量,增加一些复杂性,然后你就会开始看到一些奇怪的东西。

s指向一个包含“hello”的内存位,但并不打算包含更多的内容。这意味着您很可能会覆盖其他内容。这是非常危险的,尽管它似乎起了作用

两项意见:

  • *in*s--不是必需的。s--就足够了,因为您只想减小该值
  • 你不需要自己写strcat。它已经存在了(你可能知道,但我告诉你:-)

  • s指向一个包含“hello”的内存位,但并不打算包含更多的内容。这意味着您很可能会覆盖其他内容。这是非常危险的,尽管它似乎起了作用

    两项意见:

  • *in*s--不是必需的。s--就足够了,因为您只想减小该值
  • 你不需要自己写strcat。它已经存在了(你可能知道,但我告诉你:-)
  • 我想知道它为什么有效

    没有。它导致Ubuntu x64上出现分段错误;要让代码正常工作,它不应该只是

    将修改后的数据移动到堆栈可以绕过linux中的数据区域保护:

    int main() {
        char b[] = "hello";
        char c[] = " ";
        char *s = b;
    
        strcat(s, " world");
    
        puts(b);
        puts(c);
    
        return 0;
    }
    
    虽然只有当“world”适合堆栈数据之间未使用的空间时,您才是安全的-将
    b
    更改为“hello to”,并且linux检测到堆栈损坏:

    *** stack smashing detected ***: bin/clobber terminated
    
    我想知道它为什么有效

    没有。它导致Ubuntu x64上出现分段错误;要让代码正常工作,它不应该只是

    将修改后的数据移动到堆栈可以绕过linux中的数据区域保护:

    int main() {
        char b[] = "hello";
        char c[] = " ";
        char *s = b;
    
        strcat(s, " world");
    
        puts(b);
        puts(c);
    
        return 0;
    }
    
    虽然只有当“world”适合堆栈数据之间未使用的空间时,您才是安全的-将
    b
    更改为“hello to”,并且linux检测到堆栈损坏:

    *** stack smashing detected ***: bin/clobber terminated
    

    也许令人惊讶的是,编译器将文本
    “hello”
    分配到读/写初始化数据中,而不是只读初始化数据。你的作业会重击与该点相邻的任何东西,但你的程序又小又简单,以至于你看不到效果。(将其放在for循环中,看看您是否正在对
    “world”
    文本进行重击。)


    它在Ubuntu x64上失败,因为
    gcc
    将字符串文本放在只读数据中,并且当您尝试写入硬件MMU对象时。

    也许令人惊讶的是,您的编译器将文本
    “hello”
    分配到读/写初始化数据中,而不是只读初始化数据。你的作业会重击与该点相邻的任何东西,但你的程序又小又简单,以至于你看不到效果。(将其放在for循环中,看看您是否正在对
    “world”
    文本进行重击。)


    它在Ubuntu x64上失败,因为
    gcc
    将字符串文本放入只读数据中,并且当您尝试写入时,硬件MMU对象。

    根据C99规范(C99:TC3,6.4.5,§5),字符串文本