我正在尝试在C中实现TLV (Type-Length-Value),但是我遇到了使动态大小值正常工作的问题。
我的结构看起来像这样:
typedef struct __attribute__((packed)){
unsigned char type;
unsigned char length;
unsigned char * value;
} TLV;
我正在尝试将数组转换为结构,以便我可以轻松访问类型和长度。例如一个数组:
unsigned char test[5] = {(unsigned char)'T', 0x03, 0x01, 0x02, 0x03};
' T'在数组中是类型,第一个0x03是长度。
我正在将数组转换为结构,如下所示:
TLV* tlv = (TLV*)test;
然而,当我尝试访问值数组时,我得到了一个分段错误,即使我尝试访问值存储器地址的第一个元素(应该是长度之后的数组中的第一个元素)。
如何解决此分段错误?
答案 0 :(得分:6)
不要将值声明为指针,而是将其声明为未知大小的数组unsigned char value[]
。
typedef struct __attribute__((packed))
{
unsigned char type;
unsigned char length;
unsigned char value[];
} TLV;
执行此操作后,其余代码将按预期工作。
答案 1 :(得分:3)
value
不是数组,它是一个指针(指向结构外部的某个位置)。如果您想要数组(大小未知),请改为编写unsigned char value[1]
。
typedef struct __attribute__((packed)) {
unsigned char type;
unsigned char length;
unsigned char value[1];
} TLV;
拥有大小为1的数组允许您实际处理任意数量的字节。这实际上是UB,但它实际上已经使用并且在我看到的所有情况下都能正常工作。
GCC允许使用大小为0的数组。我已经习惯了这种惯例,我忘了在C中不允许使用大小为0的数组。
编辑:
数组和指针之间存在差异。虽然您可以使用类似的代码来处理两者,但这些仍然是不同的野兽。
免责声明:以下代码适用于gcc,但可能并非严格有效。我并没有试着让它完全有效。
让我们定义两个结构:
typedef struct {
char p[20];
} sa;
typedef struct {
char *p;
} sp;
并创建那些实例:
sa x = { "Hello, world" };
sp y = { "Howdy, world" };
这两者之间有什么区别?
printf("%s\n", x.p); // prints "Hello, world"
printf("%s\n", y.p); // prints "Howdy, world"
这些地址怎么样?
printf("address of x = %p\n", &x); // On my machine it prints 0x7fffacce9b20
printf("address of y = %p\n", &y); // 0x7fffacce9b10
嗯..不是很有趣,除了这些数字是非常相似的 - 两个结构都位于大致相同的位置 - 在我的情况下它是堆栈,地址空间的末尾,但可能在其他地方。
printf("address of x.p = %p\n", &x.p); // 0x7fffacce9b20
printf("address of y.p = %p\n", &y.p); // 0x7fffacce9b10
相同的数字。正如预期的那样。
printf("address of x.p[0] = %p\n", &x.p[0]); // 0x7fffacce9b20 - same as before
printf("address of y.p[0] = %p\n", &y.p[0]); // 0x400764 - could be anything
现在这些是不同的。字符串“Hello,world”与结构x位于同一位置,而字符串“Howdy,world”位于其他位置 - 数据段,位于地址空间的开头,但也可能位于其他位置。
所以这就是区别:数组是“在这里”存储的一些数据,而指针只是“某处”存储的数据的地址。
在您的情况下,您希望能够将数据保存在“此处” - 紧跟在类型和长度之后。这就是你需要数组而不是指针的原因。
我找不到任何证据证明上面的TLV实现不是UB,但我看到很多情况下,通过将其转换为指向某个结构的指针来“解析”字符数组。我自己甚至写了这样的代码。
正如我之前所说,C标准不允许使用大小为0的数组。但它们是GCC允许的,这很方便,因为它允许您执行以下操作:
typedef struct {
unsigned char type;
unsigned char length;
unsigned char value[0];
} TLV;
int required_length = 10;
TLV *tlv = (TLV *) malloc(sizeof(TLV) + required_length);
如果没有0大小的数组,你必须在上面的代码中的某处添加(或减去?减去我猜)1。
答案 2 :(得分:-1)
以下几乎完全是可移植的,当然不是由于别名造成的UB,因为在任何时候test
都不会被解除引用,所以你可以忘掉它。
(技术上)可移植的是假设struct
TLV
中没有内部填充。
为了获得可移植性,我删除了__attribute__((packed))
。
如果你的编译器支持它,那么你是100%明确没有UB。
除非您将value
更改为对齐类型,否则您可能会被破坏。
这一切都有效,因为sizeof(unsigned char)
必须为1,并且类型对齐必须划分它们的大小。请记住,如果某个类型T没有malloc(n*sizeof(T))
,则会将n
T
类型的unsigned char
元素数组破坏。 C标准被绘制成char
无法对齐的角落,因为将内存视为assert(.)
(任何一种)的数组始终是合法的。
因此,以下程序将在#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef struct {
unsigned char type;
unsigned char length;
unsigned char value;
} TLV;
static TLV dummy;
int main(void) {
//There's no standard way to verify this at compile time.
//NB: If you stick with packing or leave all the members of TLV the same type
//Then this is almost certainly NOT an issue.
//However the cast of test implicitly assumes the following is the case.
//Here's a run-time check of a static constraint.
assert(offsetof(TLV,value)==(sizeof(dummy.type)+sizeof(dummy.length)));
unsigned char test[5] = {(unsigned char)'T', 0x03, 0x01, 0x02, 0x03};
TLV* tlv=(TLV*)test;
for(unsigned char i=0;i<tlv->length;++i){
printf("%u\n",(&tlv->value)[i]);
}
(&tlv->value)[0]=253;
(&tlv->value)[1]=254;
(&tlv->value)[2]=255;
for(unsigned char i=0;i<tlv->length;++i){
printf("%u\n",(&tlv->value)[i]);
}
return EXIT_SUCCESS;
}
失败或成功执行。
在所有已知的平台上,它将成功执行,因为没有已知的平台可以选择内部填充给定的数据结构 - 无论您是否指定打包。
但为什么这样做:
#include <stdlib.h>
#include <stdio.h>
typedef struct {
unsigned char type;
unsigned char length;
unsigned char value[];//Variable length member.
} TLV;
int main(void) {
TLV* tlv=malloc(sizeof(TLV)+3*sizeof(unsigned char));
tlv->type='T';
tlv->length=3;
tlv->value[0]=1;
tlv->value[1]=2;
tlv->value[2]=3;
for(unsigned char i=0;i<tlv->length;++i){
printf("%u\n",tlv->value[i]);
}
tlv->value[0]=253;
tlv->value[1]=254;
tlv->value[2]=255;
for(unsigned char i=0;i<tlv->length;++i){
printf("%u\n",tlv->value[i]);
}
free(tlv);
return EXIT_SUCCESS;
}
当你能做到这一点(C99以后我被告知)并且没有糟糕的对齐问题:
value
请注意,没有合规的保证方式静态分配这些东西,因为没有合规的保证方式来指示结构的布局(特别是大小),因此您无法知道在char数组中分配多少空间。
你可以(当然)混合解决方案,但是如果打包结构,你可能会破坏TLV
的对齐(如果需要对齐的东西),如果你没有冒险编译器内部填充{{1 }}。如果你将length
升级到size_t
的类型 - 这是自然的“完整”答案,那么内部填充不太可能是当前的伪装,但实际上非常可能。
目前的长度限制为255(几乎在所有平台上)坦率地说是吝啬。 1993年在Turbo Pascal写作时感觉很卑鄙。在2015年它是piffling。至少将length
实现为`unsigned int',除非你知道这么紧的上限就足够了。