如何在C语言中检查char*是否指向字符串字面值

23

我有一个结构体

struct request {
  int code;
  char *message;
};
我想要正确地释放一个东西。我有以下函数来做这件事:
void free_request(struct request *req) {
  if (req->message != NULL) {
      free(req->message);
  }
  free(req);
  req = NULL;
}

我的问题是,当我尝试释放一个使用字符串字面值创建的请求时,编译器会报“free():无效指针”/segfault错误:

struct request *req;
req = malloc(sizeof(struct request));
req->message = "TEST";
free_request(req);

因为我想在不同的地方创建请求结构体,一次使用字面量(在客户端上),一次使用*chars(在服务器端从套接字读取)我想知道是否有一个函数可以确保我不尝试释放字面量,同时仍然允许我释放使用 malloc 创建的消息。


11
这是一个很好的“除了问题外还要陈述目标”的例子,许多新的 Stack Overflow 用户会忽略它。实际上,解决这个问题的方法是从不同角度思考,因此如果没有提供更大的目标示例,对于“如何检查 char* 是否指向文字常量?”这个问题的唯一答案将是“你不能”。 - Tyler McHenry
6个回答

26

没有标准函数可以让您知道指针是否是动态分配的。您应该在结构体中包含一个标志以通知自己,或仅使用动态分配的字符串(在这种情况下,strdup是您的朋友)。根据您的网络设置,使用strdup可能更简单(事实上,完全使用strdup更简单)。

使用strdup

struct message* req;
req = malloc(sizeof *req);
req->message = strdup("TEST");
free_request(req);

使用标志:

struct message
{
    int code;
    char* message;
    bool isStatic; // replace to 'char' if bool doesn't exist
};

void free_request(struct message* req)
{
    if (!req->isStatic) free(req->message);
    free(req);
}

struct message* req;
req = malloc(sizeof *req);
req->message = "TEST";
req->isStatic = 1;
free_request(req);

另外,创建对象时不要忘记将分配的内存清零。这可以避免许多麻烦。

req = malloc(sizeof *req);
memset(req, 0, sizeof *req);

此外,在free_request中将req设置为NULL不会产生任何影响。您需要采取struct message**或在函数调用后自行处理。


3
可以使用calloc代替mallocmemset - bstpierre
感谢您详尽的回答。我正在寻找一种解决方案,可以从结构体/释放函数的“内部”工作,即不需要使用它的人注意,但我想这并不容易实现。实际上,这是一个小型库,因此任何使其更方便使用的东西都将有所帮助。我只需确保在文档中提到,您必须传递一个指针,该指针有效可用于释放。 - Niklas
3
如果你想让它更简单,你应该创建一个函数来自动创建请求对象,并从那里复制。这样你的客户端就不必担心管理你的内存了。 - zneak
这种方法相当临时。考虑使用非文字(已知文字)和匿名提供的字符串的情况。请参阅我的帖子以获取通用解决方案。 - Noah Watkins

5

无法确定您是否正在使用字符串文字(嗯,您可以将字符串文字放置在GCC创建的自定义.section中,然后检查字符串指针以确定它是否包含在文本段中)。 但是...有一种更好的方法,使用简单的编程模式。

使用文字分配

正常情况下,调用free(req)将按预期工作:释放请求结构。

struct *req;

req = malloc(sizeof(*req));
req->message = "TEST";

使用动态字符串进行分配

以下是一个与请求消息相关的字符串some_string。它可以是文字字符串,也可以是动态分配的字符串。当结构体本身被分配时,这将为字符串分配内存(并在结构体释放时自动释放)。

struct *req;

req = malloc(sizeof(*req)+strlen(some_string)+1);
req->message = (char *)&req[1];
strcpy(req->message, some_string);

自由化

free(req);

编辑:通用情况

请注意,上面的动态字符串分配方案是通用的,即使您不知道some_string是否为字面量。因此,如果有一个函数可以同时处理这两种情况,并且使用free()进行释放可以消除特殊情况。


req->message = "TEST"; 这行代码会复制指针,当你尝试释放它时会出现问题(因为 "TEST" 没有被动态分配),req = malloc(sizeof(*req)); req->message = "TEST"; 会造成内存泄漏(内存被分配然后指针被覆盖)。你需要将 "TEST" 复制到动态分配的内存中。 - Bob Fincheimer
你应该重新考虑你的说法。(1) 我从未建议过你调用 free(req->message)。在我的分配方案中,只应该释放 struct request *,而不是它的 message 成员。(2) req->message = "TEST" 肯定不会泄漏内存。如果你仔细阅读我的帖子,你会注意到我是在提到静态分配的字符串,这些字符串是不能被释放的,因此也不可能泄漏。最后,如果你看一下我的第二部分,你就会意识到动态内存是如何发挥作用的。如果需要更多解释,请告诉我。 - Noah Watkins
我明白了,谢谢。有点复杂,但现在我知道它是如何工作的。谢谢。 - Bob Fincheimer
简单并不总是更好。这也是Linux内核中常见的编程模式。 - Noah Watkins

4
我建议在struct request中添加一个成员来指示请求是否动态分配了request::message,并在分配request::message时同时设置该成员,在释放内存之前检查它。这在C语言中有点混乱。
请注意,不仅是字符串文字会导致问题,任何指向未通过malloc()或calloc()在堆上动态分配的数据的指针都会失败,因此即使可以在C语言中侦测到"如果char指向C语言中的字符串文字",也无济于事。

2

它出现段错误是因为包含"TEST"的内存位置通常是只读的,而且不位于堆上(通常是因为它位于程序的某个只读部分)。如果只有一个char*指针,你将无法知道它是否指向可用free()释放的字符串。相反,你应该为req->message分配一个缓冲区并复制字符。

char* str = "TEST";
int len = strlen(str);
req->message = malloc(len+1);
strcpy(req->message, str);

或者,您可以像zneak建议的那样使用strdup()


2
实际上,只要它不是堆地址,它是只读的还是可写的并不重要。重要的是它不能被释放。 - torak
这并不是一个堆地址,因为它通常存放在程序的只读段中。 - In silico
@In silico:它可以放在可写的内存段中,但仍然不属于堆。重要的问题是(a)它是分配的内存段的地址吗?(b)它之前是否已经被释放了。 - David Thornley
Niklas,你没有分配这些字符串,因此释放它们是无效的。 - Jay

0
如果您只是想确保释放 malloc 分配的内存,您可以调用
realloc(req->message,(size_t)0)

如果内存库的实现是健壮的,它应该可以工作。

OP确实这样做了,但是通过调用free()来完成。问题在于内存并不是由malloc()分配的,因此调用free()realloc()会引发未定义行为的恶魔。 - RBerteig
我相信我说过“如果内存库实现是健壮的”...你曾经实现过malloc、realloc、free等吗?你必须检查每个传递的块,以确保你拥有它。与free(其返回void)不同,realloc具有(void *)返回值。任何realloc的实现都应该在无法重新分配缓冲区时返回NULL。是的,这种行为是定义好的,你可以测试它。顺便说一句,Niklas没有要求对他的设计发表评论。他问了一个简单的问题。答案是你可以用一个合适的库来实现它。 - oops
这完全是错误的。你说过“该行为已定义”。而不是,我引用一下,“如果ptr与之前由calloc、malloc或realloc函数返回的指针不匹配”。C99 7.20.3.4第3段。顺便说一下,我确实实现了一个malloc库。它没有检查你传递的块是否是堆分配的,因为如果你这样做,行为是未定义的。 - Pascal Cuoq
@PC 你说得对,我忘记了。我的实现需要符合规范,如果缓冲区无法重新分配,则必须返回null。由于它跟踪malloc的区域,因此超出范围的指针是显而易见的。在释放时,它必须抛出异常,但realloc可以优雅地失败。 - oops

0

请看一下:

struct request *req;
req = calloc(1,sizeof(struct request));
strcpy(req->message = malloc(strlen("TEST")+1),"TEST");
free_request(req);

它严格符合ANSI C标准。 strdup不是ANSI C。

req = NULL; 

是多余的。


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