分类:
2010-08-26 12:48:33
1.零长数组代码片段
#include
#include
#include
#include
struct helloworld_t
{
int num;
char helloworld[0];
};
int main()
{
struct helloworld_t *p;
unsigned int size = sizeof(struct helloworld_t) + sizeof("Hello World!\n");
p = (struct helloworld_t *) malloc(size);
if (!p)
return -1;
memcpy(p, "\x01\x00\x00\x00Hello World!\n", size);
while (p->num--)
{
printf("%s",p->helloworld);
}
free(p);
return 0;
}
---零长数组
gcc方言零长数组就是零长数组,有基址没尺寸,不是指针。#include
struct stTest
{
int iA;
int iB;
char ar[0];
int iC;
};
struct stTest stA;
int main(void)
{
printf("&stA: %p\n", &stA);
printf("&stA.ar: %p\n", &stA.ar[0]);
printf("&stA.iC: %p\n", &stA.iC);
}
复制代码&stA: 0x8049600
&stA.ar: 0x8049608
&stA.iC: 0x8049608
-----------------------一些知识
1. 这东西叫什么名字我忘了…… 有点像转义int a = '\x12';
int b = 0x12;
assert( a==b );
复制代码char const* a = "\x12\x12";
char const b[] = { 0x12, 0x12, 0x0 };
assert( strlen(a)==strlen(b) );
assert( strlen(b)+1==sizeof b );
assert( memcmp(a, b, sizeof b) == 0 );
复制代码2. 类型的表示
假设int是4字节, 且用二进制补码表示。int a = 0x120326;
// a是由4个字节组成, 从高权到低权依次是0x00, 0x12, 0x03, 0x26。
// 如果是大端, 内存中的布局是高权在低地址
unsigned char b[] = {0x00, 0x12, 0x03, 0x26 };
assert( memcmp(&a, b, sizeof a) == 0 );
// 如果是小端, 内存中的布局是低权在低地址
unsigned char b[] = {0x26, 0x03, 0x12, 0x00};
assert( memcmp(&a, b, sizeof a) == 0 );
复制代码3. 数组与指针
数组与指针的区别可以说一大堆。
但最容易理解的, 恰好是它们在结构中的不同行为。typedef struct
{
int i;
char c[8];
} A;
typedef struct
{
int i;
char* c;
} B;
复制代码还是设int是4字节, A的布局是:?? ?? ?? ?? ?? ?? ?? ?? ... ???
<- i -> <- 8字节 c ->
< A ->
复制代码再设指针也是4字节, 且对齐需求与int相同, 那B的布局是:?? ?? ?? ?? ?? ?? ?? ??
<- i -> <- c ->
< B ->
复制代码这样, 下面的代码应该就好理解了:char layout[sizeof(A)] = { 0x26, 0x03, 0x12, 0x00, 'h' , 'e' , 'l', 'l', 'o', ' ', 'c', 0 };
A a;
memcpy(&a, layout, sizeof a);
复制代码前4个字节是i, 所以复制完毕后, a.i就是0x00120326(小端, 其他假设同上)。
a.c就是余下的内容, 一个c-style-string "hello c"
将中间的layout步骤省去:A a;
memcpy(&a, "\x26\x03\x12\x00hello c\x00" , sizeof a);
复制代码就差不多是那个样子了。
最后一个\x00是多余的, "literal" 本来末尾就有一个0。
4. flexible array memberA a;
assert( a.c == (char*)&a + offsetof(A, c) );
复制代码即a.c的地址, 是a的地址加上c在A中的偏移。B b;
char* p = *(char**)( (char*)&b + offsetof(B, c) );
assert( b.c == p );
复制代码将c在B中的偏移处的内容, 理解为一个指针, 该指针的值就是b.c的地址。
故A可以存放一个整数与一个长度不超过7的c-style-string。
而B可以存放一个整数与一个指针, 指针由可以存放另一块内存的地址。
上面的a的布局是:0x26 0x03 0x12 0x00 "hello c"
<- i -> <- c ->
< a ->
复制代码而B的布局:B b;
b.i = 0x120326;
void* p = malloc( sizeof("hello c") );
b.c = p;
memcpy(p, "hello c", sizeof("hello c") );
0x26 0x03 0x12 0x00 pp pp pp pp
<- i -> <- 值是p ->
<- b ->
p :
"hello c"
复制代码a中, 0x120326和 "hello c"的地址是连续的
b中, 它们可能是分离的。
而以前的一些"聪明"的C程序员这样来实现一个变长且连续的整数、字符串pair:A* pa = malloc( offsetof(A, c) + 12 );
复制代码那么*pa的布局就是:pa:
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
<- i -> <- c -> <- rest ->
复制代码理论上, pa->c只有8字节。
但pa->c是这8字节的首地址。
再根据指针运算, pa->c[8]就是rest的首地址。
所以, 通过pa->c, 其实可以操纵rest部分。
pa就可以存放一个整数与一个长度不超过11的c-style-string。
理论上, pa->c确实是一个8字节的数组, 但被当成了12字节的数组。
虽然大部分情况下不会出问题, 但这毕竟是一个类型漏洞, 说不通。
比如, C89没有规定C实现一定不能添加数组长度检查。
如果真有C编译器去实现了, 代码就要挂。
所以, C99把这个行为标准化, 称为flexible array member。
语法是不指定维度:typedef struct
{
int i;
char c[];
} A99;
复制代码这样, 编译器必须让这种"末尾是一个不定长成员"的做法合法。
C要求数组的维度>=1, 0长数组是gcc的方言。