如何区分动态分配的字符串和字符串常量?

8
有没有一种方法(纯C语言)可以区分使用malloc分配的字符串和字符串字面量而不知道哪个是哪个?严格来说,我想找到一种检查变量是否为使用malloc分配的字符串的方法,如果是,我将释放它;如果不是,则让它保持不变。
当然,我可以倒着搜索代码并确保变量是否是使用malloc分配的,但是万一有简单的方法存在……编辑:添加了几行以使问题更具体。
char *s1 = "1234567890"; // string literal
char *s2 = strdup("1234567890"); // malloced string
char *s3;
...
if (someVar > someVal) {
    s3 = s1;
} else {
    s3 = s2;
}
// ...
// after many, many lines of code an algorithmic branches...
// now I lost track of s3: is it assigned to s1 or s2?
// if it was assigned to s2, it needs to be freed;
// if not, freeing a string literal will pop an error

9
没有任何便携方式可以做到,没有必要这样做。听起来你正在尝试错误的内存管理方法。有更好的方法可以保证内存被正确地分配和清理。第一步是不要“忘记”如何分配某个东西的内存。 - Ed S.
我不是C/C++程序员,但这似乎很明显,所以可能我漏掉了什么。无论如何,如果你需要释放s2,那么如果s3将被赋值为s1s2,而且没有其他的操作,你是否关心s3?你不会两次释放由strdup返回的字符串,因此如果你无论如何都要释放s2,那么你为什么还要关心s3呢? - Lasse V. Karlsen
2
当你用C语言编写代码时,你需要对内存的“所有者”做出强有力的保证。调用者拥有它吗?那么他们有责任释放它。被调用者拥有它吗?类似的事情。你编写的代码非常清楚地表明了所有权和控制链,并且你不会遇到“谁来释放这个?”这样的问题。正如Lasse所说,如果你在s2s3上都调用free,你最终会得到未定义的行为。 - Ed S.
@Ed S.:说得好!你真的应该把这个作为答案。 - alk
2
注意:即使 char *s1 = "1234567890" 是合法的代码,但最好写成 const char *s1 = "1234567890"。请参见 https://dev59.com/z3A75IYBdhLWcg3wboYz。 - chux - Reinstate Monica
2个回答

8
有没有一种(在纯C中)区分malloc的字符串和字符串字面量的方法?
不存在任何便携式的方法。不过不用担心,有更好的替代方案。
当你在C中编写代码时,你会对“谁”拥有内存做出强烈的保证。调用者拥有它吗?那么由他们负责释放它。被调用者拥有它吗?类似的事情。
你编写的代码非常清楚地表明了所有权和控制链,而且不会遇到“谁释放这个?”这样的问题。你不应该感到需要说:
// after many, many lines of code an algorithmic branches...
// now I forgot about s3: was it assigned to s1 or s2?

解决方案是:不要忘记!你可以控制你的代码,只需要向上查看页面一点即可。设计时要考虑内存泄露,防止将内存泄露到其他函数中,而没有明确的理解“嘿,你可以读取这个东西,但是不能保证在X或Y之后它仍然有效。这不是你的内存,请将其视为这样。”。
或许这是你的内存。例如,你调用strdup。通过文档,strdup会让你知道你有责任释放它返回给你的字符串。你如何做到这一点由你决定,但最好将其范围限制在尽可能狭窄的范围内,并且仅在必要的时间内保留它。
这需要时间和练习才能变得自然。在你擅长处理内存之前,你会创建一些处理内存不良的项目。那没关系,在开始阶段犯的错误会教会你什么是不该做的,以后你会避免重复这些错误(希望如此!)
另外,正如@Lasse在评论中所暗示的,你不必担心s3,它是一个指针的副本,而不是整个内存块。如果你在s2s3上调用free函数,你将得到未定义的行为。

补充一下:假设s1是一个字面量,而s2是一个变量(正如代码所示)。代码使用if进行分支,并根据某些条件将s1s2的值赋给s3。然后调用函数foo(s3)。现在,如果我需要在函数foo中释放s3,我就不再知道s3的任何信息了。为了确保我能够确定s3的指针值,无论它是被赋值给s1还是s2,我必须将另外两个变量(someVarsomeVal)传递给函数foo重新评估条件。只有通过传递其他变量,我才能确定s3的指针值。 - ssd
3
你描述的不是实际问题,真正的问题是你已经违反了我所说的规则。当你设计代码时,如果这种情况发生,你已经输了。你创建了一个难以维护和理解的系统。解决方案是:不要这样做。如果在函数foo中需要释放s3,你不应该这样做,因为这会让内存的责任变得模糊不清。如果foo可以接受任何字符串,则它不应该负责释放内存。 - Ed S.
2
调用foo的人负责,因为只有它知道正确的做法。你错误地将责任/所有权推给了一个无法理解其输入的函数。这就是问题所在。这是一个设计问题,而不是技术问题。看看标准库函数;你看到有多少函数需要使用char*并且还要释放它? - Ed S.

0

这里有一个实用的方法:

虽然 C 语言标准没有规定这一点,但是对于你代码中所有相同的字面字符串,编译器会在可执行映像的 RO-data 部分生成一个单一副本。

换句话说,你代码中每个字符串 "1234567890" 的出现都被翻译成相同的内存地址。

因此,在你想要释放该字符串的时候,你可以简单地比较它的地址和字面字符串 "1234567890" 的地址:

if (s3 != "1234567890") // address comparison
    free(s3);

再次强调,这不是C语言标准所强制的,但任何体面的语言编译器都会实际实现它。

更新:

以上仅是一个实用技巧,直接涉及到问题本身(而不是问题背后的动机)。

严格来说,如果您已经达到了一个需要区分静态分配字符串和动态分配字符串的实现点,那么我会猜测您最初的设计在某个地方存在缺陷。


另一种可能的“实用技术”是找出系统用于堆的地址空间的部分,并查看字符串是否在其中。 - M.M
@MattMcNabb:我也考虑过这个问题,但无法想出如何用代码表达。唯一保证可行的选择是查看您的链接器设置(或配置文件)。 - barak manos

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