1. Linux的errno和指针
常见的Linux函数返回值类型包括整型和指针,内核中这两种返回值类型的函数会互相调用。Linux的errno
为整型,为了与errno
对应,Linux将指针分为3种:
- 空指针:NULL,地址为0
- 异常指针:地址空间的高4095个字节。
0xfffff001 - 0xffffffff
(32位)和0xfffffffffffff001 - 0xffffffffffffffff
(64位) - 普通指针
errno
的最大值MAX_ERRNO
在err.h
定义,值为4095。关于变量转换类型后的值,可以使用测试程序来进行测试。
long | void *(arm) | unsigned long(arm) | void *(arm64) | unsigned long(arm64) |
---|---|---|---|---|
0 | (nil) | 0 | (nil) | 0 |
-1 | 0xffffffff | 0xFFFFFFFF | 0xffffffffffffffff | 0xFFFFFFFFFFFFFFFF |
-MAX_ERRNO | 0xfffff001 | 0xFFFFF001 | 0xfffffffffffff001 | 0xFFFFFFFFFFFFF001 |
为了处理两种数据类型的转换,内核提供了include/linux/err.h
。
1.1. 整型和指针类型转换
interface | input | output | function |
---|---|---|---|
IS_ERR_VALUE | any | bool | 强制转换为unsigned long后,判断是否大于(unsigned long)-MAX_ERRNO |
ERR_PTR | long | void * | errno转指针 |
PTR_ERR | pointer | long | 指针转errno |
IS_ERR | pointer | bool | 判断指针是否异常 |
IS_ERR_OR_NULL | pointer | bool | 判断指针是否异常或NULL |
ERR_CAST | pointer | void * | 将任意类型的指针转为void型指针 |
PTR_ERR_OR_ZERO | pointer | int | 如果是异常指针,返回指针对应的errno,否则返回0 |
2. C语言中整型数据的存储和数据类型转换原理
其实,在C语言中,数值是以补码的形式存储的,正数存储的内容就是其本身(原码),负数补码的计算方法如下:
- 取绝对值
- 取反码,也就是对每一位取反
- 对反码加1,得到补码
以char型为例,-1在内存中的表示计算方法如下:
graph TB; A["0000 0001"] -- 取反码 --> B["1111 1110"] B -- 反码加1 --> C["1111 1111"]
关于为什么使用补码存储数据,可以参整数在内存中是如何存储的,为什么它堪称天才般的设计。
2.1. 负数的强制类型转换
做强制类型转换时,就是把内存中的数据(补码)看作要转换的类型。将-1
强制转换为unsigned char
型时,就可得到值为0xFF,以此类推,可知将-
转换为unsigned long
时,值为0xFFFFFFFF
(32位)或0xFFFFFFFFFFFFFFFF
(64位)。同样的道理-4095
转换为unsigned long
时,值为0xFFFFF001
(32位)或0xFFFFFFFFFFFFF001
(64位)。需要注意的是,类型转换是临时的,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。
2.2. 强制数据类型降级
数据类型级别从高到低为:
1unsigned long long
2long long
3unsigned long
4long
5unsigned int
6int
7unsigned short
8short
9unsigned char
10char
从高级到低级转换时,超出数值位数的高位部分将被丢弃。另外,降级转换也是是临时的。
3. 测试程序
1#include <stdio.h>
2#include <string.h>
3
4#define MAX_ERRNO 4095
5
6union err_t {
7 int i;
8 long l;
9 unsigned int ui;
10 unsigned long ul;
11 void *p;
12 char ch[sizeof(unsigned long)];
13};
14
15static void test(long val)
16{
17 union err_t err;
18 memcpy(&err, &val, sizeof(val));
19
20 printf("int: %d\n", err.i);
21 printf("long: %ld\n", err.l);
22 printf("unsigned int: %u, 0x%08X\n", err.ui, err.ui);
23 printf("unsigned long: %lu, 0x%08lX\n", err.ul, err.ul);
24 printf("void *: %p\n", err.p);
25}
26
27int main(void)
28{
29 printf("\ntesting: 0\n");
30 test(0);
31
32 printf("\ntesting: -1\n");
33 test(-1);
34
35 printf("\ntesting: -MAX_ERRNO\n");
36 test(-MAX_ERRNO);
37
38#if 0
39 printf("\ntesting: -4096\n");
40 test(-4096);
41#endif
42 return 0;
43}
3.1. ARM测试结果
交叉编译后使用QEMU运行查看结果。
1arm-linux-gnueabihf-gcc -static -o err_arm err.c
2qemu-arm err_arm
1testing: 0
2int: 0
3long: 0
4unsigned int: 0, 0x00000000
5unsigned long: 0, 0x00000000
6void *: (nil)
7
8testing: -1
9int: -1
10long: -1
11unsigned int: 4294967295, 0xFFFFFFFF
12unsigned long: 4294967295, 0xFFFFFFFF
13void *: 0xffffffff
14
15testing: -MAX_ERRNO
16int: -4095
17long: -4095
18unsigned int: 4294963201, 0xFFFFF001
19unsigned long: 4294963201, 0xFFFFF001
20void *: 0xfffff001
3.2. ARM64测试结果
交叉编译后使用QEMU运行查看结果。
1aarch64-linux-gnu-gcc -static -o err_aarch64 err.c
2qemu-aarch64 err_aarch64
1testing: 0
2int: 0
3long: 0
4unsigned int: 0, 0x00000000
5unsigned long: 0, 0x00000000
6void *: (nil)
7
8testing: -1
9int: -1
10long: -1
11unsigned int: 4294967295, 0xFFFFFFFF
12unsigned long: 18446744073709551615, 0xFFFFFFFFFFFFFFFF
13void *: 0xffffffffffffffff
14
15testing: -MAX_ERRNO
16int: -4095
17long: -4095
18unsigned int: 4294963201, 0xFFFFF001
19unsigned long: 18446744073709547521, 0xFFFFFFFFFFFFF001
20void *: 0xfffffffffffff001