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