【问题标题】:What is the most efficient way of having a placeholder char* that can be sent to free()?将占位符 char* 发送到 free() 的最有效方法是什么?
【发布时间】:2019-11-19 11:45:05
【问题描述】:

我正在开发一个程序,我们正在尽我们所能获得的每一点性能,我们现在进入微优化阶段(同时避免非严格必要的代码重复)。

想象一下这样的小代码:

propertyContent = gets_x_prop( propStruct );
propertyContent = ( propertyContent ? propertyContent : strdup("") );

/// ....
/// ....
/// ....

free(propertyContent)

已经做出决定,我们确信下一个要考虑攻击的是对空字符串的 strdup 的多次调用。
请记住,我们需要在该变量中添加一个 char* 以简化随后的代码并使其更直接(避免 (void*)0 出现错误)

问题在于如何优化...
到目前为止,我们只能实现一个自定义函数:

char* a = malloc(1);
a[0] = NULL;

我们认为应该有一些替代这种方法的方法。我们还不想用执行 NULL 检查的宏替换所有 free(),因为我们认为它对我们来说更难解决。

我们自己的测量结果表明,目前,等待时间最长的是在 malloc 中,例如发生在 strdup 中的那些,其中一些是复制空字符串。

【问题讨论】:

  • 如果你有一个propertyContent,它本身就是一个空字符串,你想要释放它,除了你的静态之外你无法分辨它共享空字符串(除非您比较每个 free 之前的地址)。
  • 您应该将NULL 视为指针常量,而不是字符常量。它可能只是整数0,但它可能被定义为(void*)0。无论如何,它与 ASCII NUL(一个 L)'\0' 不是一回事。使用那个或0 作为终止零字节。尤其是在“调用free 之前”的上下文中,将其称为NULL 检查会令人困惑。我们只能从上下文中告诉您检查数据的意思,而不是指针。
  • free(NULL) 非常安全。与其分配空字符串,不如分配 NULL。
  • “将所有 free() 替换为执行 NULL 检查的宏” - 为什么? free(NULL) 在标准 C 中得到完全支持,实际上是无操作的。 TL;DR : NULL 是“这里什么都没有”的一个很好的标记,因为它永远不会更真实。
  • I'm working on a program which we are squeezing every little bit of performance we can get and we are now into the micro-optimizations phase 所以从 strdup 开始 :) 作为“宏”优化

标签: c micro-optimization


【解决方案1】:

不需要宏,也不需要空检查。 free(NULL) 定义明确、安全且无操作。

所以只需删除所有解决此问题的代码:

propertyContent = gets_x_prop(propStruct);

// …

free(propertyContent);

完成。


也就是说,现在这意味着propertyContent 可以是NULL,这意味着它的所有使用 仍然需要检查NULL,这当然远非理想。让它指向一个动态分配的空字符串使它的使用更简单,因此更安全。您需要决定是否值得放弃这种安全性。

如果您想确保非NULL 字符串但又担心堆碎片,请将对象模型替换为可以安全有效地表示空字符串的对象模型。简而言之,这意味着替换 free 如下:

// Header

extern const char EMPTY[];
void mystr_free(char *);
// Implementation
const char EMPTY[] = "";

void mystr_free(char *str) {
    if (str != EMPTY) free(str);
}

这与您的(不必要的)NULL 检查宏的方向相同,但具有使用 never-NULL 字符串的所有好处。

【讨论】:

  • str != EMPTY - 我只担心它可能不适用于不同的翻译单元(假设例如static const char *EMPTY 在标题中并且多个文件包含它并使用EMPTY)。我宁愿 extern const char *EMPTY 在标题中使用 const char *EMPTY = "" 在其中一个来源中。
  • 我个人也会保留 pointer 常量:extern char const* const Empty;,这将阻止用户修改它以指向其他地方。
  • @brunoais 我的代码中没有任何内容可以改变您拥有char* 的事实。至于重新定义free,自定义解除分配函数是很常见的,它的开销应该大大小于你对空字符串的各种mallocs 的开销。在关闭它之前仔细计时。
  • @Aconcagua:甚至更好:const char EMPTY[] = ""; 在许多情况下会更有效。当您可以使地址本身成为链接时常量时,无需使代码从内存中加载指针值。 (在 x86-64 上,可以在寻址模式下使用完整的静态地址,但不能立即使用。但在寄存器中创建地址仍然只需要 1 个额外的 RIP 相对 LEA,而不是 cmp rax, [rip + EMPTY]。和Linux 非 PIE 可执行文件可以使用 cmp rax, offset EMPTY(GAS Intel 语法),因为在这种情况下静态地址确实适合 32 位立即数。)
  • @PeterCordes 有趣的一点。由于指针现在是 const,我希望一个体面的编译器可以优化到相同的值,但是您的解决方案遵循我自己的范例:不要依赖优化,如果您可以使用 相当简单 i> 替代...
猜你喜欢
  • 1970-01-01
  • 2015-05-12
  • 2019-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多