如何设计用ANSI C编写的库?

3
我希望开发一个使用ANSI C语言的库。
我有一个字符串结构体:string
struct libme_string
{
  char* buffer;
  int length;
};

我希望编写一个名为libme_create_string()的函数,用于创建和初始化一个字符串(类似于C++中的构造函数)。
以下哪种方法更适合设计libme_create_string()

方法一

libme_create_string()中为字符串对象分配内存并返回:

struct libme_string* libme_create_string(int length)
{
  // Check arguments...

  // Allocate memory for object.
  struct libme_string* str = malloc(sizeof(struct libme_string));

  // Handle memory allocation errors...

  str->buffer = malloc(length);
  str->length = length;

  // Handle memory allocation errors...

  return str;
}

void libme_delete_string(struct libme_string* str)
{
    // Check arguments...

    free(str->buffer);
    free(str);
}

使用

struct libme_string* str;
str = libme_create_string(1024);

// ...

libme_delete_string(str);
str = NULL;

方法二

libme_create_string()函数中不要为字符串对象分配内存,而是将其作为参数接受:

struct void libme_create_string(libme_string* str, int length)
{
  // Check arguments...

  // Just allocate memory for members.
  str->buffer = malloc(length);
  str->length = length;

  // Handle memory allocation errors...
}

void libme_delete_string(struct libme_string* str)
{
  // Check arguments...

  free(str->buffer);
}

使用

struct libme_string str; // << different, not a pointer!
libme_create_string(&str, 1024);

// ...

libme_delete_string(&str);

注意事项

  • string 仅为示例。
  • 方法 #2 更快,是吗?

最后,有没有关于编写C库的良好设计指南?


@muntoo:看起来muntoo和我同时编辑了这篇文章。你的一些更改让我意外地被还原了。 - Amir Saniyan
另一个选项是在您的create函数中返回结构体本身(而不是指向它的指针)。 - Vaughn Cato
@Vaughn:但是当sizeof(struct X)是一个很大的数字时,这并不是一个好主意。 - Amir Saniyan
我认为你会发现返回结构体与方法2一样快。通过将指针传递到被调用的函数中来返回结构体。在进行优化编译时,复制也会被省略。 - Vaughn Cato
3个回答

4

个人而言,我认为第二个版本不够直观且更容易出错。

如果你正在尝试尽最大努力封装实例化过程(这是你应该做的),那么第一个版本确实是唯一可行的方式——一步完成。第二个版本意味着为了拥有一个完全初始化的变量,你不仅需要实例化它,还需要立即调用一个辅助函数。这个额外的步骤会导致潜在的错误。


版本#2在额外内存分配方面比版本#1慢吗? - Amir Saniyan
1
不管哪种方式,你基本上都会分配相同数量的内存。 - cwallenpoole

3

就个人而言,我更喜欢第一种方法。虽然有点类似于C++,但...

thing_t *thing_new(...);
void thing_delete(thing_t *ptr);

我认为所有的“大小”或“计数”成员都应该是无符号的,最好使用size_t。 另外:您最后的片段试图释放一个自动变量。这是不使用它的一个很好的理由。
编辑:
还有(至少)第三种方法:将整个对象作为值返回。我并不特别喜欢这种方法,但它至少避免了双重分配。它像这样进行:
typedef struct {
  StrLen length;
  StrType type;      /* type is not stored in the brainfile 
                     **but recomputed on   loading */
  char *word;
} STRING;

STATIC STRING new_string(char *str, size_t len)
{
STRING this;

if (str) {
     if (!len) len = strlen(str);
     if (len) { this.word = malloc(len); memcpy(this.word, str, len); }
     else { this.word = malloc(1); memset(this.word, 0, 1); }
     this.length = len;
     this.type = word_classify(this);
     }
else        {
     this.word = NULL;
     this.length = 0;
     this.type = 0;
     }
return this;
}

典型的使用方式如下:

if (*np == WORD_NIL) {
  STRING this;
  *np = dict->size++;
  this = new_string(word.word, word.length);
  dict->entry[*np].string = this;
  dict->entry[*np].hash = hash_word(this);
  }

(该代码从megahal继承,重新在wakkerbot中使用) 正如我所说,我不喜欢这种方法,但结构体赋值肯定有其优点。


你的最后一段代码尝试释放一个自动变量。谢谢,我会进行更正 :) - Amir Saniyan

2
为什么不将该过程分为两个函数,这样您就可以使用您需要的任何一个:
struct libme_string * create_string();
void destroy_string(struct libme_string *);

struct libme_string * init_string(struct libme_string * str, unsigned int length);
struct limbe_string * deinit_string(struct libme_string * str);

使用方法 #1,所有动态分配:

struct libme_string * str = init_string(create_string(), 10);
destroy_string(deinit_string(str));

用法 #2,自动外部结构:

struct libme_string str;
init_string(&str);
deinit_string(&str);

请确保init函数返回指针,这样你就可以像我一样组合调用。

如果deinit()也将指针设置为零,那么如果指针不为零,你可以让destroy()调用deinit(),尽管这会打破对称性。


当我使用init_string初始化字符串,然后做一些操作,但忘记如何实例化对象并调用destroy_string时会发生什么?我认为这种方式容易出错。 - Amir Saniyan
如果你忘记编写正确的代码,那么C语言有很多绳索可以让你自缢 :-) 如果更有用的话,你可以编写一个动态分配的单一函数来完成所有操作,另一个仅用于内部初始化。或者使用C++ :-) - Kerrek SB

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