1. Linux的errno和指针

常见的Linux函数返回值类型包括整型和指针,内核中这两种返回值类型的函数会互相调用。Linux的errno为整型,为了与errno对应,Linux将指针分为3种:

  • 空指针:NULL,地址为0
  • 异常指针:地址空间的高4095个字节。0xfffff001 - 0xffffffff(32位)和0xfffffffffffff001 - 0xffffffffffffffff(64位)
  • 普通指针

errno的最大值MAX_ERRNOerr.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. 取绝对值
  2. 取反码,也就是对每一位取反
  3. 对反码加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