在 C 中,指针的值改变后内存值会发生什么变化?

In C, what happens to memory values after a pointer's value is changed?

在下面的代码中,我将一个指针按值传递给一个函数,我希望将 N 个字符串连接到原始字符串上。

按照我编写的方式,我通过计算出要添加到原始字符串长度上的字节数来分配新内存。

然后填充这个新字符串,并return编辑指向该字符串的指针。

所以,假设原始指针 msg0x000001,它指向字符串 "Hello[=13=]".

的起始字符

然后在函数中,一个新的指针strNew最终指向"Hello world, poop[=15=]",并且值为0x0000f5,新字符串的内存所在。

然后,作为函数的 return,msg 指针的值现在是 0x0000f5

我的问题是,位于 0x000001 的内存发生了什么?它包含 "Hello[=13=]" 的字节,但不再有指向它的指针。它会收集垃圾吗?这是个问题吗?我应该用 ' ' 字符以某种方式覆盖内容吗?

如果没有,我如何从 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 中的指针需要复制才能释放。

My question is, what happens to the memory located at 0x000001?? It contains bytes for "Hello[=17=]", but there is no longer a pointer to it. Does it get garbage collected? Is it a problem? Should I overwrite the contents somehow with ' ' chars?

对于初学者来说,C 语言没有垃圾收集器。其次,您不能更改字符串文字。任何更改字符串文字的尝试都会导致未定义的行为。

在此声明中

char * msg = "Hello";

声明了一个指向字符串文字第一个字符的指针 "Hello"。字符串文字本身具有静态存储持续时间,并且在程序完成执行之前一直有效,而与指针 msg 的值是否更改无关。

你可以这样想象

char unnamed_string_literal[] = { 'H', 'e', 'l', 'l', 'o', '[=11=]' };

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', 'e', 'l', 'l', 'o', '[=14=]' };
char word2[] = { 'W', 'o', 'r', 'l', 'd', '!', '[=14=]' };

int main(void) 
{
    char *msg = word1;
    
    printf( "%s ", msg );
    
    msg = word2;
    
    puts( msg );
    
    return 0;
}

首先,如果您为“Hello[=30=]”动态分配内存并且不释放它,则会造成内存泄漏。这是一个如何制作的学校示例。

这导致下一个认识:C 中没有垃圾收集器,除非您创建一个。你对一切负责。这就是 C 语言的美妙之处,因为你也可以控制一切。您有权决定针对您的特定问题的最佳方法。

下一个问题是,在函数内部,您永远不知道内存是如何分配的。我可以在调用者级别创建 char example[100] = "My value"; 并将其分配在堆栈上。如果您尝试从函数内部释放它,程序将失败。

解决这个问题的基本方法很少:

  • 调用方提供缓冲区,如果缓冲区不够大则函数失败
  • 该函数分配内存并且不接触输入,调用者对其接收的所有内容负责。
  • 更高级别的字符串抽象(我会说 C++ 中的 class,但让我们说一些具有一些相关操作的类型)一个例子可能是 Glib String https://developer.gnome.org/glib/stable/glib-Strings.html , 但还有许多其他实现。

它们各有优缺点(防止泄漏、内存碎片等)。这是由 C 程序员来决定哪种方法最适合您的情况。

My question is, what happens to the memory located at 0x000001?? It contains bytes for "Hello[=16=]", but there is no longer a pointer to it.

什么都没发生...指针只不过是一些内存的地址,所以改变指针的值只会改变它指向的字节,它不会影响存储在指向的字节中的值到.

Does it get garbage collected?

C 没有内置的垃圾收集器;你负责管理内存。

Is it a problem?

也许……要看情况。对于 strcatcat() 函数,正如您观察到的那样,指针 orig 是按值传递的。这意味着该函数获得了它自己的指针副本,并且如果该函数更改了 orig 的值,则调用者的指针副本根本不会改变。由于 strcatcat() 没有分配 orig 指向的内存,它不负责释放它......调用者(或分配或负责该块的人)应该这样做。

如果您的程序分配内存块并且从不释放它们,那肯定是个问题,因此总的来说您应该有一个清晰的内存管理策略。但是除非您非常清楚调用 strcatcat() 会释放传入的块,否则该函数不应触及原始块。

Should I overwrite the contents somehow with ' ' chars?

你为什么要这么做?即使在调用您的函数之后,调用者是否可能还想将原始块用于其他用途?

当您解除分配时,字节不会停止存在;他们总是在那里。分配只是为特定目的保留给定字节范围的过程,这样其他一些代码就不会尝试同时使用相同的字节。当您释放内存时,字节仍保留在那里,但您释放的块可用于其他用途。如果数据在某种程度上是敏感的,例如,在释放块之前覆盖数据是有意义的。密码或某种个人信息。但是只要不需要保护块中的数据,就没有必要在释放内存之前清除内存。