How to design libraries written in ANSI C?
I want to develop a library with ANSI C.
I have a string
struct
:
struct libme_string
{
char* buffer;
int length;
};
I want to write a function, libme_create_string()
, that creates and initializes a string
(like constructors in C++).
Which of these methods is better for designing libme_create_string()
?
Method #1
Allocate memory for string object in libme_create_string()
and return it:
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);
}
Use
struct libme_string* str;
str = libme_create_string(1024);
// ...
libme_delete_string(str);
str = NULL;
Method #2
Do not allocate memory for string object in libme_create_string()
function, accept it as an argument:
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);
}
Use
struct libme_string str; // << different, not a pointer!
libme_create_string(&str, 1024);
// ...
lib开发者_StackOverflow中文版me_delete_string(&str);
Notes
string
just a sample.- Method #2 is faster, isn't it?
Lastly, are there any good design guidelines for designing libraries written in C?
Personally, I would view the second version as less intuitive and more error prone.
If you're trying your hardest to encapsulate instantiation (which you should be doing anyway), then the first really is the only way to go — one step, done. The second version means that in order to have a fully initialized variable, you need to not only instantiate it, but you need to call a helper function on it immediately. That extra step is a bug waiting to happen.
Personally I prefer the first method. Agreed: it's a bit C++ like, but ...
thing_t *thing_new(...);
void thing_delete(thing_t *ptr);
I do think that all "size" or "count" members should be unsigned, preferably size_t. Also: you last snippet tries to free() an automatic variable. That is a good reason not to use it.
EDIT:
There is (at least) a third way: return the entire object as a value. I don't particularly like the method, but it at least avoids the double allocation. It goes like this:
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;
}
Typical usage goes like 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);
}
(code inherited from megahal, reused in wakkerbot) As I said, I don't like this method, but the struct assignment definitely has its advantages.
Why not factor the process into two functions, so you can use whichever you need:
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);
Usage #1, all dynamic allocations:
struct libme_string * str = init_string(create_string(), 10);
destroy_string(deinit_string(str));
Usage #2, automatic outer struct:
struct libme_string str;
init_string(&str);
deinit_string(&str);
Make sure that the init functions return the pointer, so that you can compose the calls like I did.
If deinit()
also sets the pointer to zero, then you could make destroy()
call deinit()
if the pointer is non-zero, though that breaks the symmetry a bit.
精彩评论