我有很多函数需要一个字符串作为参数,为此我使用char*
,但我所有需要字节数组的函数也使用char*
.
问题是我很容易犯在字符串函数中传递字节数组的错误,导致各种溢出,因为找不到空终止符。
这通常是如何处理的?我可以想象将我所有的字节数组函数都更改为uint8_t
,然后编译器会在我传递一个字符串时发出有关签名的警告。或者这里的正确方法是什么?
我有很多函数需要一个字符串作为参数,为此我使用char*
,但我所有需要字节数组的函数也使用char*
.
问题是我很容易犯在字符串函数中传递字节数组的错误,导致各种溢出,因为找不到空终止符。
这通常是如何处理的?我可以想象将我所有的字节数组函数都更改为uint8_t
,然后编译器会在我传递一个字符串时发出有关签名的警告。或者这里的正确方法是什么?
我通常会制作一个类似于以下内容的数组
typedef struct {
unsigned char* data;
unsigned long length;
unsigned long max_length;
} array_t;
然后传递 array_t*
并创建采用 array_t* 的数组函数
void array_create( array_t* a, unsgined long length) // allocates memory, sets the max_length, zero length
void array_add(array_t* a, unsigned char byte) // add a byte
ETC
这个问题在 C 中比你想象的更普遍。由于char*
和char[]
等价于函数参数,因此这样的参数可以指代三个不同的语义概念:
char
对象上的指针(这是指针类型的“官方”定义)char
数组在大多数情况下,C 标准中的现代接口可能void*
用于无类型字节数组,您可能应该遵守该约定,并且char*
仅用于字符串。
char[]
本身可能很少这样使用;我无法想象这些有很多用例。如果您将它们视为数字,您应该使用signed
orunsigned
变体,如果您将它们视为位模式unsigned char
应该是您的选择。
如果您真的将数组作为函数参数(char
或不是),您可以通过清楚地指示来为您的代码的临时读者标记该事实:
void toto(size_t n, char A[const n]);
这相当于
void toto(size_t n, char *const A);
但会让你的意图更清晰。将来甚至可能会有工具为您进行边界检查。
C 使用约定。这是我使用的规则(在标准库之后流行)
void foo(char* a_string);
void bar(void* a_byte_array, size_t number_of_bytes_in_the_array);
这很容易记住。如果您传递单个 char* ptr,则它必须是一个以 null 结尾的 char 数组。
编写一个通用结构来处理字符串和字节。
struct str_or_byte
{
int type;
union
{
char *buf;
char *str;
}pointer;
int buf_length;
}
如果type
不是字符串,则访问pointer.buf
唯一的 upto buf_length
。否则直接访问pointer.str
而不检查buf_length
并将其维护为空终止字符串。
或者通过仅考虑长度将字符串也维护为字节数组,不要为字符串保留以空字符结尾的字符。
struct str_or_byte
{
char *buf;
int buf_length;
}
并且不要使用不考虑长度的字符串操作函数。这意味着使用strncpy
, strncat
, strncmp
... 而不是strcpy
, strcat
, strcmp
...