C实现是否可以在引擎盖下使用长度为前缀的字符串"?

时间:2015-05-26 15:48:26

标签: c compiler-construction compiler-optimization c-strings null-terminated

在阅读完这个问题之后:What are the problems of a zero-terminated string that length-prefixed strings overcome?我开始想知道,究竟是什么阻止了C实现为堆栈上分配的任何charwchar_t数组分配了一些额外的字节或者堆并将它们用作"字符串前缀"存储其元素的数量N

然后,如果第N个字符为'\0',则N - 1表示字符串长度。

我相信这可以极大地提升strlenstrcat等功能的效果。

如果程序广泛使用非0 - 终止的char数组,这可能会转向额外的内存消耗,但这可以通过编译器标志打开或关闭常规&#34来解决。 ;计数直到任您到达 - '\0'"编译代码的例程。

此类实施可能存在哪些障碍? C标准是否允许这样做?这种技术可以解决哪些问题我还没有解决?

并且......实际上有没有这样做过?

5 个答案:

答案 0 :(得分:5)

您可以存储分配的长度。并且malloc实现确实这样做(或者至少有一些实现)。

但是,您无法合理地存储分配中存储的任何字符串的长度,因为用户可以将内容更改为他们的心血来潮;保持最新的长度是不合理的。此外,用户可能会在字符数组中间的某处启动字符串,或者甚至可能不使用数组来保存字符串!

答案 1 :(得分:3)

  

然后,如果第N个字符为'\0',则N - 1表示字符串长度。

实际上,不,这就是为什么这个建议不起作用的原因。

如果我用0覆盖字符串中的字符,我实际上已截断字符串,并且字符串上的strlen的后续调用必须返回截断的长度。 (这通常由应用程序完成,包括由(f)lex生成的每个扫描程序,以及strtok标准库函数。等等。)

此外,在字符串的内部字节上调用strlen是完全合法的。

例如(仅用于演示目的,虽然我打赌你可以找到几乎与此相同的代码。)

/* Split a string like 'key=value...' into key and value parts, and
 * return the value, and optionally its length (if the second argument
 * is not a NULL pointer). 
 * On success, returns the value part and modifieds the original string
 * so that it is the key.
 * If there is no '=' in the supplied string, neither it nor the value
 * pointed to by plen are modified, and NULL is returned.
 */
char* keyval_split(char* keyval, int* plen) {
  char* delim = strchr(keyval, '=');
  if (delim) {
    if (plen) *plen = strlen(delim + 1)
    *delim = 0;
    return delim + 1;
  } else {
    return NULL;
  }
}

答案 2 :(得分:2)

如果有用的话,没有什么从根本上阻止你在你的应用程序中这样做(其中一条评论指出了这一点)。然而,有两个问题会出现:

  1. 您必须重新实现所有字符串处理函数,并拥有my_strlenmy_strcpy等,并添加字符串创建函数。这可能很烦人,但这是一个有限的问题。

  2. 在为系统编写时,您必须停止人员,故意或自动将关联的字符数组视为“普通”C字符串,并使用它们上的常用函数。您可能必须确保此类用法及时破裂。

  3. 这意味着,我认为,将重新实现的“C字符串”走私到现有程序中是不可行的。

    这样的东西
    typedef struct {
        size_t len;
        char* buf;
    } String;
    size_t my_strlen(String*);
    ...
    

    可能会起作用,因为类型检查会让人感到沮丧(2)(除非有人决定为效率而破解',在这种情况下,你可以做的事情并不多。)

    当然,除非你证明字符串管理是代码中的瓶颈并且这种方法可以改善事情,否则你不会这样做。

答案 3 :(得分:1)

这种方法存在一些问题。首先,您将无法创建任意长的字符串。如果您只保留1个字节的长度,那么您的字符串最多只能包含255个字符。你当然可以使用更多的字节来存储长度,但有多少? 2? 4?

如果您尝试连接两个都在其大小限制边缘的字符串(例如,如果您使用1个字节的长度并尝试将两个250个字符的字符串相互连接,会发生什么情况)该怎么办?您是否只需根据需要在长度上添加更多字节?

其次,在哪里存储此元数据?它不知何故必须与字符串相关联。这类似于Dennis Ritchie在C中实现数组时遇到的问题。最初,数组对象存储了一个指向数组第一个元素的显式指针,但是当他在语言中添加struct类型时,他意识到他不希望元数据混淆内存中struct对象的表示,所以他摆脱了它并引入了在大多数情况下数组表达式转换为指针表达式的规则。

您可以创建一个新的聚合类型,如

struct string
{
  char *data;
  size_t len;
};

但是你将无法使用C字符串库来操纵该类型的对象;实现仍然需要支持现有的接口。

您可以将长度存储在字符串的前导字节或字节中,但是您保留多少?您可以使用可变数量的字节来存储长度,但现在您需要一种方法来区分长度字节和内容字节,并且您无法通过简单地取消引用指针来读取第一个字符。像strcat这样的函数必须知道如何绕过长度字节,如果长度字节数改变了如何调整内容等。

0终止方法有其缺点,但它也更容易实现,并且使操作字符串更容易。

答案 4 :(得分:1)

标准库中的字符串方法定义了语义。如果生成包含各种值的char数组,并将指针传递给数组或其中的一部分,则根据NUL字节定义其行为的方法必须以与定义的方式相同的方式搜索NUL字节按标准。

可以定义一个自己的字符串处理方法,它使用更好的字符串存储形式,并且只是假装标准库字符串相关的函数不存在,除非必须将字符串传递给类似的东西fopen。这种方法的最大困难在于,除非使用非可移植编译器功能,否则不可能使用内联字符串文字。而不是说:

ns_output(my_file, "This is a test"); // ns -- new string

人们不得不说更像:

MAKE_NEW_STRING(this_is_a_test, "This is a test");
ns_output(my_file, this_is_a_test);

MAKE_NEW_STRING将创建匿名类型的并集,定义名为this_is_a_test的实例,并适当地初始化它。由于许多字符串将具有不同的匿名类型,因此类型检查将要求字符串是包含已知数组类型的成员的联合,并且期望字符串的代码应该被赋予指向该成员的指针,可能使用如下内容:

#define ns_output(f,s) (ns_output_func((f),(s).stringref))

可以以这样的方式定义类型,以避免需要stringref成员并让代码只接受void*,但是stringref成员基本上会执行静态鸭子类型(只有具有stringref成员的东西才能被赋予这样的宏)并且还可以允许对stringref本身的类型进行类型检查。

如果可以接受这些约束,我认为有可能编写的代码几乎在所有零终止字符串的方式上都更有效;问题是这些优势是否值得麻烦。