作者:黄兢成
链接:https://www.zhihu.com/question/348023215/answer/868039233
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
const char* 和 std::string 哪个好,要看场合。假如是 C++ 的内部类实现,优先采用 std::string,可以减少很多内存分配释放的麻烦。但假如是预先编译库的接口,提供给其他人使用,应该是封装成 C 的接口,使用 const char*。使用 C++ 风格实现,封装成 C 风格的接口。C++ 没有二进制标准。对象的二进制布局、链接名字、内存分配释放,每个编译器和标准库都有不同。假如一种编译器编译出库,导出 C++ 接口,在另一种编译器去使用,就算链接通过,运行时也可能出一些诡异的问题。不同编译器并非特指 gcc 或者 vc 完全不同种类。有时是同一个 vc 编译器,但是不同版本混用,也会出问题。甚至同一个版本的同一个编译器,Release 选项编译出来的库,在 Debug 选项中使用,还是会出问题。为了避免这些问题,预先编译的库,无论内部实现是否使用 C++,通常会导出 C 风格的接口。假如你的 C++ 库是直接提供源码,不用预先编译,将源码嵌入到用户工程,这时导出 C++ 的类也没有所谓。题目中说 “在 C++ 编程中,领导坚持用 char 而不用 string”,缺少特定的背景,不能直接判断领导的做法是否正确。评论问,C 风格返回的 char* 谁来释放?在此补充说明。原则上,内存是谁分配,就谁来释放,有时也可变通。有多种方法,究竟哪种更好需要根据场合判断。知道最大值有时,在调用接口前就知道内存最大值。可以类似这样写const char* getCurrentPath(char* buf, size_t bufsize);
char path[MAX_PATH_SIZE];
getCurrentPath(path, MAX_PATH_SIZE);
在调用之前,已经预知路径的最大长度。传入 buf 和 bufsize, 函数内部对数据进行填充。这样根本就不需要动态分配 char*, 也就没有释放问题。C 字符串最后会填充一个 0。有时这种风格的接口,第二个参数并非 bufsize,而是字符串的最大长度 max_str_len。当同一个工程使用多个库时,它们的含义可能不同,很容易弄混。因而很多人会多添加一个字节。char path[MAX_PATH_SIZE + 1];
getCurrentPath(path, MAX_PATH_SIZE);
这种接口可以返回错误码。假如根本不会发生错误,有时返回 const char*, 方便连锁调用,比如char path[MAX_PATH_SIZE + 1];
printf("%s\n", getCurrentPath(path, MAX_PATH_SIZE));
也可以将其封装成结构,比如typedef struct {
char path[MAX_PATH_SIZE];
size_t size;
} Path;
const char* getCurrentPath(Path* path);
Path path;
getCurrentPath(&path);
不知道最大值不知道最大值时,就只能动态分配了。一种风格是让用户调用两次,比如int getTitle(char* buf, int len);
int len = getTitle(NULL, 0);
char* buf = malloc(len + 1);
getTitle(buf, len);
xxx
free(buf);
在调用之前,还不知道最终长度。于是用户先使用 NULL 调用一次 getTitle(NULL, 0),返回需要的内存大小。之后根据大小动态分配内存,这时内存的分配和释放都交给了用户。这种风格挺常见的,通常需要两次调用。这种风格,返回的 int 有多个含义,通常返回负数表示失败。另一种可选的风格是,库内部临时管理内存,用户访问结果。假如要将结果保存下来以后使用,用户就自己分配内存拷贝。比如typedef struct {
char* result;
size_t size;
} Base64Context;
Base64Context* base64_allocContext();
void base64_freeContext();
int base64_decode(Base64Context* ctx, const char* data, size_t size);
const char* base64_getResult(Base64Context* ctx, size_t* size);
////////////////////
Base64Context* ctx = base64_allocContext();
base64_decode(ctx, data1, data1_size);
const char* ret1 = base64_getResult(ctx, &size1);
xxxxx
base64_decode(ctx, data2, data2_size);
const char* ret2 = base64_getResult(ctx, &size2);
xxxx
base64_freeContext(ctx);
这种风格中,会先有更高一层,类似 context 的东西, 在里面管理返回的 char*。每次 decode 的结果,都先放到库自己管理的内存中,之后调用 getResult 获取。于是同一个内存,可在多次 decode 中复用。每调用一次 decode, 会将之前的结果冲掉。在释放 context 的时候,再释放 char*。假如嫌上面两种风格麻烦,还可以在接口中注明,让用户自己释放,比如:// 此函数的返回结果,需要调用 base64_free 释放。
const char* base64_decode(const char* data, size_t size);
void base64_free(void* p) {
free(p);
}
////////////
const char* ret = base64_decode(data, size);
xxxx
base64_free(ret);
这样调用够简单,但貌似违背了谁分配,谁释放的原则。但稍微变通一下,将原则理解成,谁用这函数谁释放。特别注意,虽然 base64_free 中只调用了 free,但也需要特别提供这个函数。不能让用户写成const char* ret = base64_decode(data, size);
xxxx
free(ret);
因为库编译时 malloc、free,跟用户代码中的 malloc、free 有可能不同。而 malloc、free 需要严格匹配。用 base64_free 包装着 free, 放到库的 .c 中编译,这时的 free 就是库编译时的 free。但假如让用户自己调用 free,就是用户代码中的 free,有可能跟 base64 库中使用的 malloc 不匹配。
文章评论