是否可以在C中修改char字符串?


80

我一直在努力处理各种与指针有关的C教程和书籍,但是我真正想知道的是,一旦创建了char指针,是否有可能更改它。

这是我尝试过的:

char *a = "This is a string";
char *b = "new string";

a[2] = b[1]; // Causes a segment fault

*b[2] = b[1]; // This almost seems like it would work but the compiler throws an error.

那么,有什么办法可以改变字符串中的值而不是指针地址?

Answers:


157

当您在源代码中编写“字符串”时,它会直接写入可执行文件中,因为在编译时需要知道该值(有一些工具可以将软件拆开并在其中找到所有纯文本字符串)。当您编写时char *a = "This is a string",“ This is a string”的位置在可执行文件中,并且该位置a指向该文件在可执行文件中。可执行映像中的数据是只读的。

您需要做的(正如其他答案所指出的那样)是在堆或堆栈框架中的非只读位置创建该内存。如果声明一个局部数组,则在堆栈上为该数组的每个元素留出空间,并将字符串文字(存储在可执行文件中)复制到堆栈中的该空间。

char a[] = "This is a string";

您还可以通过在堆上分配一些内存来手动复制该数据,然后使用strcpy()来将字符串文字复制到该空间中。

char *a = malloc(256);
strcpy(a, "This is a string");

每当您使用完分配的空间后就malloc()记得记得调用free()它(请阅读:内存泄漏)。

基本上,您必须跟踪数据的位置。每当您在源代码中写入一个字符串时,该字符串都是只读的(否则,您可能会更改可执行文件的行为-设想如果先编写char *a = "hello";然后更改a[0]'c',然后再写其他地方printf("hello");。如果允许更改第一个的字符"hello",并且您的编译器仅将其存储一次(应该存储),然后printf("hello");输出cello!)


12
上一节向我详细解释了为什么它必须是只读的。谢谢。
CDR

1
-1:不告诉您使用const char *,也不能保证文字字符串存储在可执行内存中。
巴斯蒂安·莱纳德(BastienLéonard),2009年

我不是你不需要const作为我给出的两个解决方案-同样,如果字符串在编译时是已知的,并且已编译为可执行文件-它将存储在什么地方?在gcc中,如果我写char * a =“ hallo”;或char b [] =“ hello。” ;,则程序集输出“ LC0:.ascii” Hallo。\ 0“ LC1:.ascii” Hello。\ 0“”都在可执行内存中...什么时候不是?
卡森·迈尔斯

1
刚在GCC 4.4上尝试过,它将文字字符串放入.rodata(只读数据)中。我检查了objdump和程序集列表。我认为该标准不要求文字字符串为只读,因此我认为它们甚至可以放在.data中。
巴斯蒂安·莱纳德(BastienLéonard),2009年

另外,在不将指针限定为const方面,我看不出任何优势。如果以后您决定更改字符串,它可能会隐藏错误。
巴斯蒂安·莱昂纳德(BastienLéonard)2009年

29

不可以,您不能修改它,因为字符串可以存储在只读存储器中。如果要修改它,可以使用数组代替,例如

char a[] = "This is a string";

或者,您可以使用malloc分配内存,例如

char *a = malloc(100);
strcpy(a, "This is a string");
free(a); // deallocate memory once you've done

5
为了完成代码,最好还添加free()调用。
Naveen

15

很多人对char *和char []与C中的字符串文字结合起来感到困惑。当您编写时:

char *foo = "hello world";

...实际上是将foo指向恒定的内存块(实际上,在这种情况下,编译器对“ hello world”所做的操作与实现有关。)

相反,使用char []告诉编译器您要创建一个数组并使用“ hello world”内容填充它。foo是指向char数组的第一个索引的指针。它们都是char指针,但是只有char []会指向本地分配的可变内存块。


7

a和b的内存不是由您分配的。编译器可以自由选择一个只读存储位置来存储字符。因此,如果尝试更改它,可能会导致段错误。因此,我建议您自己创建一个字符数组。就像是:char a[10]; strcpy(a, "Hello");


1
字符数组的问题在于,我将char数组的指针传递给一个函数,这样我可以在那里处理一个字符串,然后再次将其发送出去。看起来我很不幸不得不使用malloc。
马修·斯托帕09年

1
不,您仍然可以使用在堆栈上分配的对象。例如,如果您有一个函数void f(char * p); 然后从main()您可以传递f(a)。这会将第一个字符的地址传递给函数。同样,如果您决定使用malloc(),则不要忘记使用free()释放内存。
Naveen

5

看来您的问题已得到回答,但现在您可能想知道为什么char * a =“ String”存储在只读存储器中。嗯,实际上c99标准没有定义它,但是大多数编译器选择这种方式来处理实例,例如:

printf("Hello, World\n");

c99标准(pdf) [第130页,第6.7.8节]:

声明:

char s[] = "abc", t[3] = "abc";

定义“普通”字符数组对象s和t,其元素用字符串文字初始化。此声明与char相同

s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };

数组的内容是可修改的。另一方面,声明

char *p = "abc";

定义类型为“ char的指针”的p并将其初始化为指向长度为4的类型为“ char的数组”的对象,该对象的元素使用字符串文字进行初始化。如果试图使用p来修改数组的内容,则该行为是不确定的。


4

您还可以使用strdup

   The strdup() function returns a pointer to a new string which is a duplicate of the string  s.
   Memory for the new string is obtained with malloc(3), and can be freed with free(3).

以您为例:

char *a = strdup("stack overflow");

不是问题的答案,而是一个非常方便的功能,谢谢!
mknaf

1
+1教给我有关strdup。我不确定何时要使用它。
Z玻色子2014年

当您执行诸如之类的操作时var = malloc(strlen(str) + 1); strcpy(var, str);,您可能应该strdup改用。
MaximeChéramy2014年

3

所有这些都是很好的答案,解释了为什么您不能修改字符串文字,因为它们被放置在只读存储器中。但是,当推到推时,有一种方法可以做到这一点。看看这个例子:

#include <sys/mman.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int take_me_back_to_DOS_times(const void *ptr, size_t len);

int main()
{
    const *data = "Bender is always sober.";
    printf("Before: %s\n", data);
    if (take_me_back_to_DOS_times(data, sizeof(data)) != 0)
        perror("Time machine appears to be broken!");
    memcpy((char *)data + 17, "drunk!", 6);
    printf("After: %s\n", data);

    return 0;
}

int take_me_back_to_DOS_times(const void *ptr, size_t len)
{
    int pagesize;
    unsigned long long pg_off;
    void *page;

    pagesize = sysconf(_SC_PAGE_SIZE);
    if (pagesize < 0)
        return -1;
    pg_off = (unsigned long long)ptr % (unsigned long long)pagesize;
    page = ((char *)ptr - pg_off);
    if (mprotect(page, len + pg_off, PROT_READ | PROT_WRITE | PROT_EXEC) == -1)
        return -1;
    return 0;
}

我将其写成是我对const-correctness的更深入思考的一部分,您可能会发现它很有趣(我希望:))。

希望能帮助到你。祝好运!


请注意,更改字符串文字是未定义的行为。
Steohan

0

您需要将字符串复制到另一个非只读的内存缓冲区中,然后在其中进行修改。使用strncpy()复制字符串,使用strlen()检测字符串长度,使用malloc()和free()动态为新字符串分配缓冲区。

例如(类似于伪代码的C ++):

int stringLength = strlen( sourceString );
char* newBuffer = malloc( stringLength + 1 );

// you should check if newBuffer is 0 here to test for memory allocaton failure - omitted

strncpy( newBuffer, sourceString, stringLength );
newBuffer[stringLength] = 0;

// you can now modify the contents of newBuffer freely

free( newBuffer );
newBuffer = 0;

0
char *a = "stack overflow";
char *b = "new string, it's real";
int d = strlen(a);

b = malloc(d * sizeof(char));
b = strcpy(b,a);
printf("%s %s\n", a, b);

6
malloc还需要1个字节。不要忘记strcpy期望的NULL终止字符,并且也会复制。这是一个非常常见的错误。
xcramps
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.