3

读完这个问题后:长度前缀字符串克服了以零结尾的字符串的问题是什么?我开始怀疑,究竟是什么阻止了 C 实现为堆栈或堆上分配的任何charwchar_t数组分配一些额外的字节并将它们用作“字符串前缀”来存储N其元素的数量?

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

我相信这可以极大地提高函数的性能,例如strlenor strcat

0如果程序广泛使用非终止数组,这可能会导致额外的内存消耗char,但这可以通过编译器标志打开或关闭已编译代码的常规“count-until-you-reach- '\0'”例程来解决。

这种实施可能存在哪些障碍?C标准是否允许这样做?这种技术会导致哪些我没有考虑到的问题?

而且……这真的做过吗?

4

5 回答 5

5

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

但是,您不能合理地存储存储在分配中的任何字符串的长度,因为用户可以随心所欲地更改内容;保持最新的长度是不合理的。此外,用户可能会在字符数组中间的某个位置开始字符串,或者甚至可能不使用数组来保存字符串!

于 2015-05-26T15:54:50.617 回答
4

然后,如果N-th 字符是'\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;
  }
}
于 2015-05-26T16:24:57.297 回答
3

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

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

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

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

就像是

typedef struct {
    size_t len;
    char* buf;
} String;
size_t my_strlen(String*);
...

可能会起作用,因为类型检查会令人沮丧(2)(除非有人决定“为了效率”而破解东西,在这种情况下你无能为力)。

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

于 2015-05-26T16:09:32.983 回答
1

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

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

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

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

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

但是您将无法使用 C 字符串库来操作该类型的对象;实现仍然必须支持现有接口。

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

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

于 2015-05-26T16:29:05.940 回答
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)。

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

于 2015-05-26T16:42:00.557 回答