问题描述
在下面的代码中,我将一个指针按值传递给一个函数,我希望将 N 个字符串连接到原始字符串上。
按照我编写的方式,我通过计算要添加到原始字符串长度上的字节数来分配新内存。
然后填充这个新字符串并返回指向该字符串的指针。
所以,假设原始指针 msg
是 0x000001
,它指向字符串 "Hello\0"
的起始字符。
然后在函数中,一个新的指针 strNew
最终指向 "Hello world,poop\0"
,其值为 0x0000f5
,即新字符串的内存所在的位置。
然后,作为函数的返回,msg
指针的值现在是 0x0000f5
。
我的问题是,位于 0x000001
的内存会发生什么变化??它包含 "Hello\0"
的字节,但不再有指向它的指针。它会收集垃圾吗?这是个问题吗?我应该用“ ”字符以某种方式覆盖内容吗?
如果没有,我如何从 strcatcat() 函数中释放它?
这个想法是不必担心有一个足够大的字符数组来开始字符串。这不合理吗?
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
char * strcatcat(char * orig,const char* strArgs,...){
va_list valist;
unsigned long initStrLength = strlen(orig);
// work out how much me to realloc
unsigned long moreBytes = 0;
va_start(valist,strArgs);
const char* str = strArgs;
while (str != NULL ) {
moreBytes += strlen(str);
str = va_arg(valist,const char *);
}
// define a new char pointer to populate,of defined size
char * strNew = NULL;
strNew = (char *) malloc((moreBytes + initStrLength+1));
// copy the original string into the start
strcpy(strNew,orig);
//reset,then go through and concat into new string
va_start(valist,strArgs);
str = strArgs;
while (str != NULL ) {
strcat(strNew,str);
str = va_arg(valist,const char *);
}
// close list
va_end(valist);
// return this pointer
return strNew;
}
int main()
{
char * msg = "Hello";
msg = strcatcat(msg," World,","poop",NULL);
printf("%s\n",msg);
return 0;
}
编辑:谢谢大家,已经解决了。我已经习惯了像 PHP、C# 等更高级别的语言,并且阅读有关指针算术的内容时会弹出这个问题,我找不到不关注指针而不是指针值的答案。
未来人的TLDR - 如果没有调用者管理它,我给出的例子会导致内存泄漏。 main 中的指针需要复制才能释放。
解决方法
我的问题是,位于 0x000001 的内存会发生什么??它包含“Hello\0”的字节,但不再有指向它的指针。
什么都没有发生...指针只不过是某个内存的地址,因此更改指针的值只会更改它指向的字节,它不会影响存储在指向的字节中的值。
它会被垃圾收集吗?
C 没有内置垃圾收集器;您负责管理内存。
有问题吗?
也许……这取决于情况。在您的 strcatcat()
函数的情况下,指针 orig
是按值传递的,正如您所观察到的。这意味着函数获得了它自己的指针副本,如果函数改变了 orig
的值,调用者的指针副本根本不会改变。由于 strcatcat()
没有分配 orig
指向的内存,因此它不负责释放它……调用者(或分配或以其他方式负责该块的人)应该这样做。>
如果您的程序分配了内存块并且从不释放它们,那肯定是一个问题,因此总体而言,您应该有一个明确的内存管理策略。但是除非您非常清楚调用 strcatcat()
将释放传入的块,否则该函数不应触及原始块。
我应该用“ ”字符以某种方式覆盖内容吗?
你为什么要这样做?即使在调用您的函数之后,调用者是否也可能希望将原始块用于其他用途?
当你释放它们时,字节不会停止存在;他们总是在那里。分配只是为特定目的保留给定字节范围的过程,以便其他一些代码不会尝试同时使用相同的字节。当您释放内存时,字节保留在那里,但您释放的块可用于其他用途。如果数据在某种程度上敏感,则在释放块之前覆盖数据确实有意义,例如密码或某种个人信息。但是只要不需要保护块中的数据,就不需要在释放之前清除内存。
,我的问题是,位于 0x000001 的内存会发生什么??它 包含“Hello\0”的字节,但不再有指向它的指针。 它会收集垃圾吗?这是个问题吗?我应该覆盖 内容以某种方式带有“ ”字符?
对于 C 语言初学者来说,没有垃圾收集器。其次,您可能不会更改字符串文字。任何更改字符串文字的尝试都会导致未定义的行为。
在本声明中
char * msg = "Hello";
声明了一个指向字符串文字 "Hello"
的第一个字符的指针。字符串文字本身具有静态存储持续时间,并且在程序完成执行之前一直有效,而与指针 msg
的值是否发生变化无关。
你可以这样想象
char unnamed_string_literal[] = { 'H','e','l','o','\0' };
int main( void )
{
char *msg = unnamed_string_literal;
//...
}
你不应该担心字符串文字。
例如考虑以下有效程序。
#include <stdio.h>
int main(void)
{
char *msg = "Hello";
printf( "%s ",msg );
msg = "World!";
puts( msg );
return 0;
}
程序输出为
Hello World!
本质上这个程序与以下程序相似(除了问题上下文中的一些不重要的细节)
#include <stdio.h>
char word1[] = { 'H','\0' };
char word2[] = { 'W','r','d','!','\0' };
int main(void)
{
char *msg = word1;
printf( "%s ",msg );
msg = word2;
puts( msg );
return 0;
}
,
首先,如果您为“Hello\0”动态分配内存并且不释放它,则会造成内存泄漏。这是一个如何制作一个学校的例子。
这导致了下一个认识:除非您创建垃圾收集器,否则 C 中没有垃圾收集器。你对一切负责。这就是 C 的美妙之处,因为您还可以控制一切。您有权决定针对您的特定问题的最佳方法。
下一个问题是从函数内部你永远不知道内存是如何分配的。我可以在调用者级别创建 char example[100] = "My value";
并在堆栈上分配它。如果你试图从你的函数内部释放它,程序就会失败。
解决这个问题的基本方法很少:
- 调用者提供了一个缓冲区,如果缓冲区不够大,函数就会失败
- 该函数分配内存并且不接触输入,调用者对其接收的所有内容负责。
- 更高级别的字符串抽象(我会说是 C++ 中的一个类,但让我们说一些具有一些相关操作的类型)一个例子可能是 Glib String https://developer.gnome.org/glib/stable/glib-Strings.html ,但还有许多其他实现。
它们中的每一个都有其优点和缺点(防止泄漏、内存碎片等)。这取决于 C 程序员来决定哪种方法最适合您的情况。