- 论坛徽章:
- 2
|
回复 100# pandaiam
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 member
- A 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的方言。 |
|