【问题标题】:Disabling NUL-termination of strings in GCC在 GCC 中禁用字符串的 NUL 终止
【发布时间】:2009-11-20 17:32:40
【问题描述】:

是否可以在 GCC 中全局禁用以 NUL 结尾的字符串?

我使用的是我自己的字符串库,我完全不需要最后的 NUL 字符,因为它已经在内部存储了适当的长度。

但是,如果我想附加 10 个字符串,这意味着在堆栈上不必要地分配了 10 个字节。对于宽字符串,情况更糟:至于 x86,有 40 个字节被浪费;对于 x86_64,80 字节!

我定义了一个宏来将那些堆栈分配的字符串添加到我的结构中:

#define AppendString(ppDest, pSource) \
  AppendSubString(ppDest, (*ppDest)->len + 1, pSource, 0, sizeof(pSource) - 1)

使用sizeof(...) - 1 效果很好,但我想知道是否可以摆脱 NUL 终止以节省几个字节?

【问题讨论】:

  • 空字符串应该怎么办?你打算如何对付他们?
  • 我很确定 80 字节对您的程序来说不会成为问题。尤其是在 x86 上。
  • 祝你好运,用你自己的版本替换 C 中的字符串函数。以及您将 nul 终止的字符串传递给的所有函数。并获得足够的性能。您能否详细解释一下您的问题,以免因试图通过在字符串开头分配两个字节来保存一个字节而大喊大叫?
  • 确实,这显然是过早的优化。即使许多语言解释器定义了自己的字符串类型,在许多情况下它们仍然包含 0 字节,因为与偶数长度字段/malloc 开销相比,它可以忽略不计。
  • 为什么,以 Charles Babbage 的名义,您是否关心像这样节省空间?除非你正在处理一些你没有提到的非常奇怪的问题,否则你不需要这样做,而且你会给自己带来比你解决的问题更多的问题。

标签: c gcc null-terminated


【解决方案1】:

这很糟糕,但你可以明确指定每个字符数组常量的长度:

char my_constant[6] = "foobar";
assert(sizeof my_constant == 6);

wchar_t wide_constant[6] = L"foobar";
assert(sizeof wide_constant == 6*sizeof(wchar_t));

【讨论】:

  • 你可以把它变成一个宏:#define NEW_STRING(var, val) char var[sizeof(val)-1] = val
  • 如果您自己的字符串类型被定义为{length; pointer} 的结构,那么您不妨使用length=0 和pointer=NULL——访问指针无论如何都是无效的,因为没有字符可以阅读。
  • @intgr:使用您的代码,我得到:“错误:从非宽字符串初始化的宽字符数组”@Sinan Ünür:即使是空字符串也是 NUL 终止的。这意味着 sizeof() - 1 按预期结果为 0。就我而言, AppendSubString() 将立即停止并且不添加任何内容。 @Matt B.:我知道,但实际上它并没有解决我的问题。 AppendString() 宏在我的代码中无处不在:AppendString(&string, "Hello World"); 如您所见,我没有为此使用任何变量。
  • @timn:哎呀,我忘了你需要在宽字符文字前加上 L"" 前缀。我更新了我的示例。
  • @timn:在字符串文字的情况下(就像您引用 AppendString(&string, "Hello World")),"Hello World" 存储在堆栈中(至少对于 x86* )。它存储在可执行文件的只读段中,AppendString 接收指向该段的指针。因此,在这种情况下,您不会增加堆栈大小,而是增加可执行文件大小。我当然希望在任何一种情况下一个字节都不会破坏你,但这甚至不是你担心的那个。
【解决方案2】:

我知道您只处理程序中声明的字符串:

 ....
 char str1[10];
 char str2[12];
 ....

而不是使用 malloc() 和朋友分配的文本缓冲区,否则 sizeof 不会帮助你。

无论如何,我会三思而后行删除 \0:您将失去与 C 标准库函数的兼容性。

除非您要为您的库重写任何单个字符串函数(例如 sprintf),否则您确定要这样做吗?

【讨论】:

  • 我没有使用堆分配的字符串,因为这正是我的字符串库的用途。手动分配内存太危险了,因为总是存在缓冲区溢出的风险。如上所述,重写字符串函数并不难。感谢 C99,我可能会使用一个简单的 hack 来保持与 Glibc 函数的兼容性: char tmp[string->len + 1]; tmp[string->len + 1] = '\0'; printf("%s", tmp);
  • 我相信您有充分的理由这样做,但是,如果我了解缓冲区溢出漏洞的工作原理,那么将字符串放在堆栈上比将它们分配在堆上更危险。关于在末尾添加 '\0',我看不到字符串的内容是如何复制到 temp 中的,我认为您使用的是 gcc 扩展而不是 C99。
【解决方案3】:

我不记得细节,但是当我记得时

char my_constant[5]

它有可能无论如何都会保留 8 个字节,因为有些机器无法寻址一个单词的中间。

几乎总是最好将这类事情留给编译器,让它为您处理优化,除非有非常好的理由这样做。

【讨论】:

  • 称为“对齐”(也称为“内部碎片”)。确实,丢弃 NUL 字节并不会减少大多数字符串,但是当它这样做时,它会减少对齐大小。因此,平均而言,每个字符串仍会少消耗 1 个字节的内存。
【解决方案4】:

如果您没有使用任何处理字符串的标准库函数,您可能会忘记 NUL 终止字节。

没有strlen(),没有fgets(),没有atoi(),没有strtoul(),没有fopen(),没有printf()%s转换说明符...

仅用所需的空间声明您的“不完全是 C 字符串”;

struct NotQuiteCString { /* ... */ };

struct NotQuiteCString variable;
variable.data = malloc(5);
data[0] = 'H'; /* ... */ data[4] = 'o'; /* "hello" */

【讨论】:

  • 基本上这或多或少是我目前正在做的事情。通常保持 NUL 终止可能确实更好,但也许有类似编译指示的东西允许我启用/禁用给定代码部分的 NUL 终止字节,即我的字符串库函数?
  • 只是不要使用它。您每天都使用int 的数组,每天有很多时间......而且您甚至从未使用过终止int。对char 数组执行相同操作(当我这样做时,我专门“签署”我的chars:signed charunsigned char:对我来说,只有普通的char 可以是C 字符串)。跨度>
【解决方案5】:

确实,这只是在你内存不足的情况下。否则我不建议这样做。

您所说的最合适的做法似乎是:

  • 以以下形式准备一些最小的“列表”文件:
string1_constant_name "str1" string2_constant_name "str2" ...
  • 构造实用程序来处理您的文件并生成声明,例如
const char string1_constant[4] = "str1";

当然我不建议手动操作,否则在任何字符串更改后都会遇到麻烦。

因此,由于固定的自动生成的数组,现在您有两个非终止字符串,并且每个变量都有 sizeof()。这个解决方案似乎可以接受。

优点是易于本地化,可以添加一定程度的检查以降低此解决方案的风险并节省 R/O 数据段。

缺点 需要在每个模块中包含所有此类字符串常量(如包含以保持 sizeof() 已知)。所以这只有在你的链接器合并了这些符号时才有意义(有些没有)。

【讨论】:

  • 好主意!不幸的是,它要求我总是在编译之前通过“预处理器”提取整个代码。如果 GCC 中真的没有关闭 NUL 终止的选项,我会坚持我目前的方法。
【解决方案6】:

这些不是类似于 Pascal 风格的弦乐或 Hollerith 弦乐吗?我认为这仅在您确实希望字符串数据保留 NULL 时才有用,在其中您实际上是在推动任意内存,而不是“字符串”本身。

【讨论】:

  • 是的,我使用它们的方式与 Hollerith 字符串类似。在我的代码中定义如下: typedef struct { unsigned int len;无符号整数 maxLen;字符缓冲区[0]; } 还有其他优点。 Wikipedia(取自“字符串文字”)说: * 消除了文本搜索(用于分隔符),因此需要的开销显着减少 * 避免了(100% 程序员引起的)分隔符冲突问题 * 允许包含元字符,否则可能会误认为命令 * 可用于非常有效的纯文本字符串数据压缩
【解决方案7】:

该问题使用了错误的假设 - 它假设存储长度(例如,通过将其作为数字隐式传递给函数)不会产生开销,但事实并非如此。

虽然不存储 0 字节(或 wchar)可能会节省空间,但大小必须存储在某处,并且示例暗示它作为常量参数传递给某处的函数,这几乎肯定会占用更多空间,在代码中。如果多次使用同一个字符串,则开销是每次使用,而不是每个字符串。

拥有一个使用 strlen 确定字符串长度且未内联的包装器几乎肯定会节省更多空间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-16
    • 2014-08-18
    相关资源
    最近更新 更多