realloc() 导致程序停止

realloc() causes program to stop

我正在学习 C,尽管 运行 使用 realloc 函数解决了一个小问题。

下面的代码旨在创建两个结构,每个结构包含一个字符列表,然后将第二个字符列表添加到第一个字符列表的末尾,为此重新分配内存。

然而,此代码执行到 realloc 调用,但随后以退出代码 0 结束,而没有完成程序的其余部分。

我无法弄清楚这里发生了什么,将不胜感激任何帮助。

#include <stdio.h>
#include <stdlib.h>

typedef struct String {
    char* chars;
} String;

String createString(char* chars) {
    String res;
    res.chars = chars;

    return res;
}

int main() {
    printf("Starting program!\n");

    String a = createString("Hello ");
    String b = createString("There");

    puts(a.chars);
    puts(b.chars);

    int aLength = sizeof(&a.chars) / sizeof(char);
    int bLength = sizeof(&b.chars) / sizeof(char);

    a.chars = (char*) realloc(a.chars, aLength + bLength);

    // Add b to the end of a
    for (int i = 0; i < bLength; i++) {
        a.chars[i + aLength] = b.chars[i];
    }

    puts("Complete");
    puts(a.chars);

    return 0;
}

createString()内替换

  res.chars = chars;

来自

  if (NULL != chars)
  {
    res.chars = malloc(strlen(chars) + 1);

    if (NULL == res.char)
    {
      perror("malloc() failed");
      exit(EXIT_FAILURE);
    }

    strcpy(res.chars, chars);
  }

还要考虑

还创建另一个函数 destroyString(String res),如下所示:

void destroyString(String * pres)
{
  free(pres->chars);
  pres->chars = NULL;
}

并在不再需要 "Strings" 时调用它。

除上述之外,您还应该始终测试相关函数调用的成功或失败。

在您的代码中,这特别指的是对 realloc().

的调用

所以你可能想替换:

  a.chars = (char*) realloc(a.chars, aLength + bLength);

来自

  {
    void * pv = realloc(a.chars, aLength + bLength);
    if (NULL == pv)
    {
      perror("realloc() failed");
      exit(EXIT_FAILURE);
    }

    a.chars = pv; /* In C there is *no* need to cast void-pointers! */
  }

C 是一种相对较低级别的语言,它使我们更接近我们所使用的机器的裸芯片,因此围绕变量存储的概念一开始可能会令人困惑。特别是当你处理字符串时。如果您打算擅长 编写 C 语言代码,那么对这些要素进行更深入的了解将很重要——正如我们假设您打算的那样。

就字符串而言,您需要了解 3 种存储方式; 'literals''automatic' 变量,以及 动态 分配。这是三种令人印象深刻的不同动物。将它们混合在一起既不明智,也不可能。

  1. 文字。当您使用 "Hello ""There" 等语句声明常量字符串时,您正在创建字符串 'literals'。这些是不可更改的实体,硬编码到最终可执行文件中并存储在特定段中,该段未设计为在 运行 时间更改。大多数操作系统甚至不允许您这样做:当您这样做时,您的代码会遇到段错误。您通常也不希望这样做,因为如果这样做,您可能会覆盖其他对您来说很重要的事情。
  2. 'Automatic'变量分配在cpu寄存器或栈上。当 C 最初被构思出来时,这个 space 受到了深刻的限制。今天,虽然仍然有限,但已不那么重要了。当您声明一个具有显式大小的字符数组时,您通常会创建一个基于堆栈的变量:char a[16];

    这样做既简单又方便,因为您无需自己负责清理,但它有几个缺点。数组创建后无法更改其大小,更重要的是;因为它会在声明它的函数退出时消失,所以不可能 return 函数中的内容。

    • 稍后,您可能会调查允许我们在一定程度上规避这些限制的扩展程序(例如 alloca)。
  3. 动态分配 将变量存储在 'heap' 上,这代表可用内存的大部分.这是 mallocrealloc 和公司所在的位置。参加进来。创建和使用这些变量更复杂,但也更强大。正如您在代码中所展示的那样;这些函数 return 指向所请求内存的指针(地址)。

    不过,在实例化这些时必须特别小心。这正是您的代码 运行 误入歧途的地方。

你很清楚字符串只是一个字符序列。当您打算修改字符串时,您必须使用足够大的 'automatic' 变量来包含您打算容纳的最长系列,或者使用堆。

你必须而不是做的是获取文字的地址,然后尝试写入或附加到该地址。

您需要修改 createString 以通过创建必要的存储并将源字符串复制到新缓冲区中来解决这个问题。在您的函数中,您在堆栈上分配 res,然后按值 returning 该变量——这意味着编译器将创建一个副本。但是,它只会复制结构本身;它不会分配任何指针,也不会为您复制它们的内容。由于您的结构只有一个指针宽,returning by-value 可能会按您的意图工作,但做一些不同的事情可能会更稳定。您使用的成语有点像 C++ 和 C 的混合体,很可能以有问题的方式结束。

在 C 中,最好将变量的声明与其实例化分开。

数组大小。 某些编译器也可以帮助您解决这个问题,尤其是 literals,但一般来说,使用 sizeof(x) 来获取 C 字符串的长度。如果您使用标准的 C 方法以零终止字符串,那么 strlen() 函数将按照您的预期达到该目的。当您声明文字时,编译器会自动为您添加该终止符。

或者,您可以自己跟踪长度(可能还有容量),并将其存储在结构中。

您还需要制作一个补充 destroyString 函数来 return 系统内存,否则您的应用程序可能会导致内存 'leak' -- 无法释放内存已分配。

鉴于您选择这种工作方式,那么也可以创建一个 appendString 函数来执行相应的任务。

在每种情况下,将指向对象的指针传递给函数更可靠——就像 C++ 等在幕后所做的那样。

所以,在main中:分别声明和初始化对象,然后适当地使用和销毁它们。

String a, b;

createString( &a, "Hello " );
createString( &b, "There" );

appendString( &a, &b );

puts( a.chars );

destroyString( &a );
destroyString( &b );

并且在文件的前面声明了实现函数。

void createString( String *s, char* chars ) {
   int len = strlen( chars );
   s->chars = malloc( len + 1 );   // strlen does not count the terminating null
   if( s->chars ) {  // make sure the pointer is valid: malloc may fail
      for( int i = 0; i <= len; ++i )  // make sure to copy the terminator as well
         s->chars[i] = chars[i];
   }
}

void appendString( String *a, String *b ) {
   int alen = strlen( a->chars ), 
       blen = strlen( b->chars );
   char *tmp = realloc( a->chars, alen + blen + 1 );
   if( tmp ) {
      a->chars = tmp;   // realloc will have copied the buffer for you
      for( int i = 0; i <= blen; ++i )
         a->chars[alen+i] = b->chars[i];  // start at the position of the terminator in 'a'
   }
}

void destroyString( String *s ) {
   free( s->chars );
}

当然,此代码还有许多重要的进一步改进。我们希望您能在发现它们的过程中获得乐趣...