C sprintf 数组字符指针

19

有人可以告诉我我在这里做错了什么吗?为什么我的程序会 segfault? 我正在尝试在string1string2之间插入第三个字符串。

#include <stdio.h>

int main (void) 
{
char *string1 = "HELLO";
char *string2 = "WORLD";
char *stringX  = "++++";
char *string3;
printf ("%s,%s\n",string1,string2);
sprintf(string3,"%s%s%s",string1,stringX,string2);
printf ("NewVar: %s",string3);
}

为什么sprintf不能将结果存储在指向string3的内存地址中?当我将string3声明为普通数组时,它可以工作,但是当它是指向char数组的指针时就不行了。

我以为string3没有指向任何内存位置,但是当我执行printf("%p",string3);时,它确实指向某个位置。

输出:

# ./concat
HELLO,WORLD,0x40042

2
只是一个提示:数据应该写在哪里?你认为指针指向哪里? - glglgl
@glglgl - 我原以为string3没有指向任何东西,但是当我执行printf ("%p",string3)时,它似乎指向了一个内存位置。 - user2953313
但是对于一个未指定的... - glglgl
@user2953313:你在说哪个内存地址?string3并没有指向任何有意义的内存地址。它的值是不确定的,从技术上讲根本不是内存地址。它可能“看起来”指向了一个“内存位置”,但实际上这只是一种幻觉 - 未定义行为的结果。 - AnT stands with Russia
5个回答

42

想象一下,你有一堆现金要放进公文包中。你需要什么?你必须测量现金的大小,以确定使用多大的公文包,并且您需要一个手柄来方便地携带现金。

现金就是你的字符串。公文包就是内存空间。公文包手柄就是指针。

  1. 测量你的现金: strlen(string1) + strlen(string2) + strlen(stringX)。将其称为“总和”。
  2. 现在获得足够大的公文包: malloc(total+1)
  3. 然后在上面放置一个手柄: string3

将所有这些组合在一起...

char *string3 = malloc(strlen(string1)+strlen(stringX)+strlen(string2)+1);
sprintf(string3, "%s%s%s", string1, stringX, string2);

那第一次尝试有什么问题呢?你没有公文包。你有现金和一个提手,但是中间没有公文包。它看起来可以工作,但是只是随机的方式,因为编译器给了你一个脏垃圾箱来存放现金。有时候垃圾箱有空间,有时候没有。当没有空间时,我们称之为“分段错误”。

每当你有数据时,你就必须为该数据分配空间。编译器会为你的常量字符串(例如"HELLO")分配空间。但是,你必须为运行时构建的字符串分配空间。


string3 没有指向任何可写内存。这里 char* 的大小并不重要 - sprintf 将尝试写入到 string3 指向的位置,而不是尝试写入到指针本身。 - simonc
大家好,感谢你们的解释。当我使用%p打印string3时,为什么它会指向一个内存位置?根据上面的评论,它不应该这样。我已经编辑了我的代码以突出这一点。 - user2953313
@user2953313,C语言不会为您的变量初始化任何特定值。指向的地址不能保证,但很可能是之前写入该堆栈位置的任何内容。尝试引用未初始化的指针会导致未定义的行为。有时它会崩溃;其他时候它可能看起来正常工作。 - simonc
@user2953313 要进行初始化,请使用calloc()而不是malloc。将其融入我的比喻中,这就像是使用旧的脏公文包(malloc)和使用新的干净公文包(calloc)之间的区别。我首先使用calloc,然后如果性能成为问题并且干净度不重要,我会切换到malloc。 - bishop
如果string1string2不是一个字符串,而是一个整数,怎么办?你无法确定数字的长度。 - jsmith
@jsmith 好的,OP声明了char *string1等等,所以你问的实际上是一个单独的问题。然而,int input; char string1[22]; snprintf(string1, 22, "%0d", input); 然后进行一些修剪以达到最小长度。 - bishop

10

sprintf确实会将值存储在那里。问题在于指针string3具有未初始化的值,因此您只是覆盖随机内存。

您可以选择使用静态字符串缓冲区的一个选项:

char string3[20];
snprintf(string3, sizeof(string3), "Hello!");

或者,在基于GNU libc的系统上,您可以使用asprintf来自动分配适当的空间:

char * string3;
asprintf(&string3, "Hello!");
// ... after use
free(string3); // free the allocated memory

1
你的asprintf示例不正确 - string3应该是char *而不是char。另外值得注意的是,asprintf不是标准C函数(或者我认为甚至不是Posix)。 - Nigel Harper
@NigelHarper:谢谢,已修复。asprintf是GNU扩展,也注意到了。 - che

9

sprintf不会为它所写的字符串分配内存。您必须提供一个有效的字符串才能将其写入,但是当前正在将未初始化的指针传递给它。

最简单的解决方法是更改:

char *string3;
sprintf(string3,"%s%s%s",string1,stringX,string2);

to

char string3[200];
sprintf(string3,"%s%s%s",string1,stringX,string2);

在这种情况下,您可能需要使用 snprintf 来防止缓冲区溢出。

char string3[200];
snprintf(string3,sizeof(string3),"%s%s%s",string1,stringX,string2);

另外一种方法是在运行时确定string3的大小,以处理更长的源字符串,注意在使用完后要free内存。

char* string3 = malloc(strlen(string1) + strlen(stringX) + strlen(string2) + 1);
if (string3 == NULL) {
    // handle out of memory
}
sprintf(string3,"%s%s%s",string1,stringX,string2);
...
free(string3);

感谢您使用malloc的答案。这是为了空字符+1吗? - user2953313
@user2953313 很高兴能帮到你。是的,+1 是为了空终止符。 - simonc

5

如果您需要将 string3 放在堆上,则需要使用 malloc 分配空间;如果不需要,则可以将其声明为字符数组。


0
假设您将i定义为int i; 在这个级别上,您告诉计算机我将存储整数,但是变量i中还没有有意义的数字。 就像当您定义char *string3时,您告诉计算机string3将存储字符指针,但仍然没有有意义的地址。因此,您必须为此变量分配内存。
string3 =  malloc(strlen(string1)+strlen(stringX)+strlen(string2)+1);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接