用C语言创建动态内存分配字符串生成器[已关闭]

jchrr9hc  于 2023-02-15  发布在  其他
关注(0)|答案(2)|浏览(106)
    • 已关闭**。此问题需要超过focused。当前不接受答案。
    • 想要改进此问题吗?**更新此问题,使其仅关注editing this post的一个问题。

7天前关闭。
Improve this question
在C语言中最痛苦的工作之一就是找出最好和最有效的处理字符串的方法。我已经搜索和尝试了将近一天了。不管怎么说,基本上,我试图创建一个动态字符串构建器函数,所以我得到了以下结果:

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

void *alloc(void **ptr, size_t len) {
  free(*ptr);
  void *mem = malloc(len);
  if (mem == NULL) {
    fprintf(stderr, "err: alloc failed");
    exit(EXIT_FAILURE);
  }
  return mem;
}

char *str(char **src, const char *fmt, ...) {
  if (fmt == NULL) {
    return NULL;
  }
  va_list args;
  va_start(args, fmt);
  size_t len = vsnprintf(NULL, 0, fmt, args);
  if (len < 0) {
    return NULL;
  }
  *src = alloc((void **) src, sizeof(char) * (len + 1));
  vsnprintf(*src, len + 1, fmt, args);
  va_end(args);
  return *src;
}

以下是我目前的使用方法:

int main(int argc, char *argv[]) {
  char *s = str(&s, "FOUND!");
  puts(s);
  puts(str(&s, "NOT %s", s));
  puts(s);
  return 0;
}

当它确实起作用时,我在想这样的事情:

  • 可能的内存泄漏。
  • 以及我在动态内存分配方面做错的任何事情。
  • 以及如何用str(&pointer, "value")初始化char *(传递未初始化的&pointer是好还是坏)。

代码是否有任何错误以及可能的修复/改进?

编辑

也许我一开始就不该这么做XD
根据答案/建议修复:

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

void *alloc(void *ptr, size_t len) {
  void *mem = realloc(ptr, len);
  if (mem == NULL) {
    free(ptr);
    perror(NULL);
  }
  return mem;
}

char *str(char *src, const char *fmt, ...) {
  if (fmt == NULL) {
    return NULL;
  }
  va_list args1, args2;
  va_copy(args2, args1);
  va_start(args1, fmt);
  int len = vsnprintf(NULL, 0, fmt, args1);
  va_end(args1);
  if (len < 0) {
    return NULL;
  }
  char *val = alloc(src, len + 1);
  va_start(args2, fmt);
  int res = vsnprintf(val, len + 1, fmt, args2);
  va_end(args2);
  free(src);
  if (res < 0) {
    free(val);
    return NULL;
  }
  return val;
}

int main(int argc, char *argv[]) {
  char *s = str(NULL, "FOUND!");
  puts(s);
  free(s);
  s = str(s, "NOT %s", s);
  puts(s);
  free(s);
  return 0;
}

变更内容(以及注解(Ns),供将来参考):

      • N**:传递未初始化的&pointer是错误的,实际上是伪装的错误。
      • N**:使用realloc代替malloc,后者处理NULL初始值并释放先前分配的内存。
  • **参数更改为***背后的最初想法是能够直接修改函数中*src指针的值(在原始代码中以某种方式工作)。
  • 初始声明更改为char *s = str(NULL, "value"),更新值需要重新分配s = str(s, "updated: %s", s)
  • 对于vsnprintf的返回值,将size_t len更改为intsize_t是我最初用来匹配mallocsize_t参数的值。
      • N**:第二次vsnprintf调用可能在第一次调用后返回不同的结果,因此使用va_copy复制第一个va_list用于第二次调用。
  • free旧字符串,然后返回实际值。
jq6vz3qz

jq6vz3qz1#

虽然它确实有效
不,它在某些微妙的方面失败了。

    • 内存释放太早**

str(&s, "NOT %s", s)执行sfree(),然后尝试读取salloc()(及其free())出现在第二个vsnprintf()之前。这是基本设计问题。

    • 不定值**

代码因char *s = str(&s, "FOUND!");而失败,因为str()最终尝试free(*ptr);,并且*ptr的值不确定。

    • 缺少va_end()**

代码应始终在va_start()之后调用va_end()

if (len < 0) {
  va_end(args); // add
  return NULL;
}
    • 高级:malloc(0)**

这里使用的alloc()从不调用malloc(0)。然而,作为一个通用函数,alloc(..., 0)是可能的。在这种情况下,malloc(0),返回NULL不是一个问题,代码应该不会出错。

    • 高级:第二个vsnprintf()可能失败**

在多线程应用程序中,第二个vsnprintf()可能返回与第一个不同的值。代码也可以检查第二个返回值,但我们遇到了一些深层次的问题。

    • 类型错误**

size_t len = vsnprintf(NULL, 0, fmt, args); if (len < 0)永远不为true。请通过保存为带符号类型来检查vsnprintf()的返回值。
迂腐:vsnprintf()返回一个int。在极少数实现中,size_t的范围比int窄。您是否应该处理这样的问题:

//size_t len = vsnprintf(NULL, 0, fmt, args);
//if (len < 0) {

int len = vsnprintf(NULL, 0, fmt, args);
if (len < 0 || len >= SIZE_MAX) {

改进:

  • 编译时启用所有警告。这将节省时间。
  • alloc()free(*str)是陌生的,然后不更新*str
  • [Edit]代码需要在打印后 * 释放

OP希望执行str(&s, "NOT %s", s),这要求s在整个打印阶段保持稳定。
代码然后必须为新字符串分配,打印,然后释放旧字符串。
'alloc()'看起来几乎像'realloc()'。
相反,返回一个错误状态并使用strdup()(或处理NULL的类似函数)进行初始化。
替代未测试代码:

// Return error flag
bool alloc_alt(void **ptr, size_t len) {
  if (len > 0) {
    void *mem = realloc(*ptr, len);
    if (mem == NULL) { 
      return true;
      // or
      fprintf(stderr, "err: alloc(%p, %zu) failed", (void*)ptr, len);
      exit(EXIT_FAILURE);
    }
    *ptr == mem;
  } else {
    free(*ptr);
    *ptr == NULL;
  }
  return false;
}

使用str()时,即使fmt == NULLvsnprintf()出现故障,也要考虑更新*src-可能是free(*src); *src = NULL;

huwehgph

huwehgph2#

你的函数alloc()str()看起来不错。你也许应该考虑使用realloc(),它可以给予你一个小的内存使用/速度改进。
你必须确保你从来没有传递一个未初始化的指针到str(),否则你的应用程序将崩溃。任何新的字符串指针应该在调用函数之前初始化为零。你为什么要传递一个指针到函数的指针?毕竟,你是要得到结果字符串,按值传递初始指针将有助于缓解这个问题,例如:

// expecting a pointer, not a pointer to pointer.
void* alloc(void* p, int len) {
    void* result;
    result = realloc(p, len);
    if (!result) {
        free(p);
        exit(1);
    }
    return result;
}

char *str(char *src, const char *fmt, ...) {

    char* result;

    // figure out len

    // ...

    result = (char*)alloc(src, len + 1);

    // fill the result string

    // ...

    return result;
}

// now str() can also be called as:

// ...

char* s;

s = str(NULL, "hello %s", "Mike");

// ...

free(s);

你担心内存泄漏是对的。你用str()创建的每一个字符串都必须用free()释放。

相关问题