是在函数内部重新声明一个结构更好,还是声明它是静态的并每次都设置为 0 更好?
Is it better to redeclare a struct inside function or declare it static and set to 0 everytime?
基本上,如果我有这样的结构:
struct header {
char ptr[512];
};
我有这样的功能:
void some_function() {
struct header header = { 0 };
// do something with struct
}
这样做真的会对性能有好处吗:
void some_function() {
static struct header header;
memset((char *)&header, 0, sizeof(header));
// do something with struct
}
我知道如果结构包含指针,memset 并不总是有效,因为 NULL 可能不位于地址 0x0000,但对于这种情况,这无关紧要,更好的方法是什么?
如果C 程序指定了一个实际的静态对象或一个自动分配在堆栈上的对象,那么这两段代码的性能将几乎相同。根据处理方式的不同,可能存在微小的性能差异,或者根据相对于其他数据和缓存属性的恰好分配位置,可能存在一些性能差异。 (特别是,自动版本可能具有更好的属性,因为内存不是专门为结构保留的。它会在其他函数执行时与其他数据共享,而不是 some_function
,因此它可能更多地驻留在缓存中经常并导致更少的内存访问。此外,由于它将与其他函数共享,整个程序可能使用更少的内存,从而提高性能。)
但是,C 程序不直接指定计算机必须做什么(尽管某些 C 实现可能以这种方式实现,或者有开关来执行它,或类似的东西)。根据 C 标准,C 程序指定抽象机中的虚构计算。 C 编译器的工作是将该计算转换为适用于真实机器的程序。这样做有很大的自由度。
这意味着如果编译器看到并充分分析了足够多的源代码,就会发现函数的两个版本的行为相同(根据可观察到的行为), 它可以将它们翻译成相同的代码。 (可观察到的行为包括输入和输出交互、访问易失性对象以及写入文件的数据。)在这种情况下,没有性能差异。
如果有的话,编译器更容易分析自动版本。它知道当函数结束时自动对象将消失(在抽象机器中)。虽然,在这两种情况下,您都在函数开始时清除了对象,因此编译器假设已经内置了有关 memset
的知识,就知道每次函数开始时对象都在这方面重新开始,有编译器编写者必须担心的行为可能不同的其他方式。例如,如果获取静态结构的地址,特别是如果它被传递给任何其他例程,编译器必须担心其中的数据可能会在函数 returns 之后被其他代码使用保留了它的地址。相反,对于自动结构,编译器可能表现得好像在函数 returns 之后从未使用过自动对象,因为在抽象机中,它在函数 returns 时不复存在。 (因此,如果任何其他代码确实保留了它的地址,则该地址的使用不是由 C 标准定义的,编译器不必为它做任何事情。)
因此,除了在深奥的情况下或内存和缓存行为的偶然事件外,我们通常可以预期自动版本至少与静态版本一样好。
一般来说,编写软件来表达你需要的东西——而且只表达你需要的东西。如果一个对象不需要在函数的生命周期之后持续存在,那么将其保留为自动对象并且不要将其设为静态。
请注意,通常没有必要将所有此类结构清零,因为:
- 使用的结构部分可能用长度或标记(例如标记结束的空字符)指示,因此没有软件会尝试读取任何后面的部分,因此不需要对其进行初始化。
- 或者,如果要读取所有结构,则可以将软件设计为填充非零部分,然后仅将剩余部分置零,而不是首先将整个结构置零。
基本上,如果我有这样的结构:
struct header {
char ptr[512];
};
我有这样的功能:
void some_function() {
struct header header = { 0 };
// do something with struct
}
这样做真的会对性能有好处吗:
void some_function() {
static struct header header;
memset((char *)&header, 0, sizeof(header));
// do something with struct
}
我知道如果结构包含指针,memset 并不总是有效,因为 NULL 可能不位于地址 0x0000,但对于这种情况,这无关紧要,更好的方法是什么?
如果C 程序指定了一个实际的静态对象或一个自动分配在堆栈上的对象,那么这两段代码的性能将几乎相同。根据处理方式的不同,可能存在微小的性能差异,或者根据相对于其他数据和缓存属性的恰好分配位置,可能存在一些性能差异。 (特别是,自动版本可能具有更好的属性,因为内存不是专门为结构保留的。它会在其他函数执行时与其他数据共享,而不是 some_function
,因此它可能更多地驻留在缓存中经常并导致更少的内存访问。此外,由于它将与其他函数共享,整个程序可能使用更少的内存,从而提高性能。)
但是,C 程序不直接指定计算机必须做什么(尽管某些 C 实现可能以这种方式实现,或者有开关来执行它,或类似的东西)。根据 C 标准,C 程序指定抽象机中的虚构计算。 C 编译器的工作是将该计算转换为适用于真实机器的程序。这样做有很大的自由度。
这意味着如果编译器看到并充分分析了足够多的源代码,就会发现函数的两个版本的行为相同(根据可观察到的行为), 它可以将它们翻译成相同的代码。 (可观察到的行为包括输入和输出交互、访问易失性对象以及写入文件的数据。)在这种情况下,没有性能差异。
如果有的话,编译器更容易分析自动版本。它知道当函数结束时自动对象将消失(在抽象机器中)。虽然,在这两种情况下,您都在函数开始时清除了对象,因此编译器假设已经内置了有关 memset
的知识,就知道每次函数开始时对象都在这方面重新开始,有编译器编写者必须担心的行为可能不同的其他方式。例如,如果获取静态结构的地址,特别是如果它被传递给任何其他例程,编译器必须担心其中的数据可能会在函数 returns 之后被其他代码使用保留了它的地址。相反,对于自动结构,编译器可能表现得好像在函数 returns 之后从未使用过自动对象,因为在抽象机中,它在函数 returns 时不复存在。 (因此,如果任何其他代码确实保留了它的地址,则该地址的使用不是由 C 标准定义的,编译器不必为它做任何事情。)
因此,除了在深奥的情况下或内存和缓存行为的偶然事件外,我们通常可以预期自动版本至少与静态版本一样好。
一般来说,编写软件来表达你需要的东西——而且只表达你需要的东西。如果一个对象不需要在函数的生命周期之后持续存在,那么将其保留为自动对象并且不要将其设为静态。
请注意,通常没有必要将所有此类结构清零,因为:
- 使用的结构部分可能用长度或标记(例如标记结束的空字符)指示,因此没有软件会尝试读取任何后面的部分,因此不需要对其进行初始化。
- 或者,如果要读取所有结构,则可以将软件设计为填充非零部分,然后仅将剩余部分置零,而不是首先将整个结构置零。