因为我的cmets越来越长,这里有一个完整的答案:
您的char * 缓冲区应该在前 X 个字节中存储字符串的长度(就像 Pascal 的做法一样)。在该长度之后是字符串数据,它可以包含您喜欢的任何字符。之后,接下来的 X 个字节告诉您 next 字符串的长度。以此类推,直到结尾,由空字符串分隔(即最后 X 个字节声称下一个字符串的长度为零,您的应用程序将此作为停止寻找更多字符串的信号)。
一个好处是您不需要扫描字符串数据 - 从第一个字符串的开头查找下一个字符串需要 O(1) 时间,查找列表中有多少个字符串需要 O(n ) 时间,但仍然会非常快(如果 O(n) 不可接受,您可以解决这个问题,但我认为现在不值得这样做)。
另一个好处是字符串数据可以包含任何你喜欢的字符。这可能是一个骗局——如果你的字符串可能包含 NUL 字符,你可以安全地提取它,但你必须小心不要将它传递给 C 字符串函数(如 strlen() 或 strcat()),它会看到NUL 字符作为数据的结尾(它可能是也可能不是)。您将不得不依赖 memcpy() 和指针算法。
问题在于 X 的值(用于存储字符串长度的字节数)。最简单的是 1,它将绕过所有字节顺序和对齐问题,但会将您的字符串限制为 255 个字符。如果这是您可以忍受的限制,那太好了,但 255 对我来说似乎有点低。
X 可以是 2 或 4 个字节,但您需要确保您的(无符号)数据类型至少有那么多字节(stdint.h 的 uint16_t 或 uint32_t,或者可能是 @ 987654328@ 或 uint_least32_t)。更好的解决方案是创建X = sizeof(size_t),因为size_t 类型保证能够存储您想要存储的任何字符串的长度。
拥有X > 1 引入对齐,如果网络可移植性是一个问题,则引入字节序。将前 X 个字节读取为 size_t 变量的最简单方法是将您的 char * 数据转换为 size_t * 并取消引用。但是,除非您可以保证您的 char * 数据正确对齐,否则这将在某些系统上中断。即使您确实保证了 char * 数据的对齐,您也必须在大多数字符串的末尾浪费一些字节来确保下一个字符串的长度值是对齐的。
克服对齐的最简单方法是将第一个 sizeof(size_t) 字节手动转换为 size_t 值。您必须决定是否要以小端或大端方式存储数据。大多数计算机本机都是 little-endian,但对于手动转换,这无关紧要 - 只需选择一个。数字 65537 (2 ^ 16 + 2) 存储在 4 个字节中,大端,看起来像 { 0, 1, 0, 2 };小端,{ 2, 0, 1, 0 }.
一旦你决定了(没关系,选择你喜欢的那个),你只需将数据的前 X 个点投射到 unsigned chars,然后投射到 size_t,然后进行位移通过适当的指数将它们放在适当的位置,然后将它们加在一起。在上面的例子中,0 将乘以 2 ^ 32、1 乘以 2 ^ 16、0 乘以 2 ^ 8、2 乘以 2 ^ 0(或 1),得到 0 + 65536 + 0 + 2 或 65537。可能有如果您进行手动转换,大端和小端之间的效率差异将为零 - 我想(再次)指出,据我所知,选择完全是任意的。
进行手动转换避免了对齐问题,并且完全绕过了对跨系统字节序的担忧,因此从小端计算机传输到大端计算机的数据将被读取相同。数据从sizeof(size_t) == 4 的系统传输到sizeof(size_t) == 8 的系统仍然存在潜在问题。如果这是一个问题,您可以 a) 抛弃 size_t 并选择一个不变的大小,或者 b) 将发送者的 sizeof(size_t) 的值编码(您只需要一个字节)作为数据的第一个字节,并让接收器进行任何必要的调整。选择 a) 可能更容易,但可能会导致问题(如果您选择的尺寸太小而无法容纳网络上的旧计算机,并且随着它们被逐步淘汰,您开始没有空间来存储您的数据?),所以我更喜欢选择 b),因为它可以随您运行的任何系统(16 位、32 位、64 位,甚至未来 128 位)进行扩展,但您可能不需要这种努力.
</vomit>我把它留给读者来整理我刚刚写的所有乱七八糟的东西。