在for循环中使用C结构体的以下两种方式中的哪一种是首选的?



假设我们想在一个包含结构体的for循环中做一些操作。这个结构体将包含一个指向char的指针元素。所以是这样的形式:

struct foo
{
char *s;    
}foo;
struct foo *new_foo(){
struct foo *myfoo = malloc(sizeof(foo));
myfoo->s = NULL;

return myfoo;
}
void free_foo(struct foo *myfoo){
free(myfoo->s);
}

for循环的基本结构是给定一些变量(取决于循环索引),它计算一些东西并返回关于foo的信息(然后这可能被定向到一些输出或用于其他计算,但我不会详细说明)。有两种方法可以实现这种效果,一种是通过返回结构体foo的函数,另一种是通过返回结构体foo *的函数。为了演示一个例子(虽然是一个愚蠢的例子),可以是:

struct foo foo_fun1(int size)
{
int i;
struct foo myfoo;
myfoo.s = calloc(size+1, sizeof(char));

for (i=0; i<size; i++){
myfoo.s[i] = 'A';
}

return myfoo;

}
struct foo *foo_fun2(int size)
{
int i;
struct foo *myfoo = new_foo();
myfoo->s = calloc(size+1, sizeof(char));

for (i=0; i<size; i++){
myfoo->s[i] = 'A';
}

return myfoo;

}

如果这样使用,它们达到相同的效果而没有任何泄漏:

int main() {
int i;

struct foo myfoo1;
struct foo *myfoo2 = NULL;

for (i=0; i<10; i++){

myfoo1 = foo_fun1(i);
myfoo2 = foo_fun2(i);

free_foo(&myfoo1);
free_foo(myfoo2);
free(myfoo2);

}


return 0;
}

在更现实的场景中,这个for循环可能会运行几百万次(但不是用这些函数!),并且创建的结构可能包含多个不同的长度的字符串,可能多达几百个字符。我的问题是在风格和效率方面,一个人会考虑一种方法而不是另一种方法吗?

在for循环中使用C结构体的以下两种方式中的哪一种是首选的?(原文如此)

显然,您需要考虑两种截然不同的选择:

一个是通过返回结构体foo和另一个结构体foo *的函数。

返回struct foo的重要特征包括:

  • 被调用的函数不会产生为结构分配空间的成本。

  • 结构从被调用函数复制到调用方。如果结构体很大,这种复制可能代价很高,但如果结构体很小,则相当便宜。

  • 调用者不需要获得释放结构本身的义务(尽管它可以获得释放其内容的义务,如在示例中)

  • 对象的生命周期(如果有的话)由调用者控制,调用者在该对象的生命周期中存储结构数据。

返回struct foo *的重要特征包括:

  • 被调用的函数(可能)会产生为结构分配空间的成本。

  • 只有指向结构体的指针被复制回调用者。无论结构的大小,这都是相当便宜的。

  • 假设指向的结构是动态分配的,如示例中所示,调用方除了可能获得释放内容的义务外,还需要获得释放结构本身的义务。

  • 存储结构的对象的生命周期由被调用的函数控制(并且假定它是"分配的")。持续时间,虽然它不一定是)。

我的问题是风格或效率明智的一个会考虑一个方法而不是另一个?

假设示例驱动程序代码反映了预期的用法,因为结构对象不需要在创建它们的循环体执行结束后存活,我倾向于节省分配和释放许多struct foo对象的成本,因为动态内存分配相对昂贵。如果struct foo很小,那么这将意味着您的第一个选择,返回struct foo。但是如果struct foo很大,那么我会考虑使用初始化函数而不是对象创建函数:

void foo_fun3(struct foo *the_foo, int size) {
char *s = malloc(size + 1);
if (s) {
memset(s, 'A', size);
s[size] = '';
}

the_foo->s = s;  
}
// ...
// usage:
void f(void) {
struct foo a_foo;
struct foo *a_foo_pointer = malloc(sizeof(*a_foo_pointer));
foo_fun3(&a_foo, A_SIZE);
foo_fun3(a_foo_pointer, A_SIZE);
// ...
}

这种方法的优点是调用方控制是否执行整个结构的内存分配,并且避免了将结果从被调用函数复制回调用方的成本。

在我看来,这三种方法在风格上都很好,尽管在我的经验中,结构返回方法比其他两种使用得少。就效率而言,我倾向于认为尽可能合理地避免动态分配可能对您的性能有利,但是您应该始终通过测试来回答性能问题。

谈到避免动态分配,您应该考虑是否也可以避免分配结构成员(s)。如果可以为内部缓冲区的大小设置一个上限,那么在示例代码中确实应该能够做到这一点,但在实际场景中可能会有额外的考虑。

相关内容

  • 没有找到相关文章

最新更新