文章目录
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在内存中的表示计算方法如下:
关于为什么使用补码存储数据,可以参整数在内存中是如何存储的,为什么它堪称天才般的设计。
2.1. 负数的强制类型转换
做强制类型转换时,就是把内存中的数据(补码)看作要转换的类型。将-1
强制转换为unsigned char
型时,就可得到值为0xFF,以此类推,可知将-
转换为unsigned long
时,值为0xFFFFFFFF
(32位)或0xFFFFFFFFFFFFFFFF
(64位)。同样的道理-4095
转换为unsigned long
时,值为0xFFFFF001
(32位)或0xFFFFFFFFFFFFF001
(64位)。需要注意的是,类型转换是临时的,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。
2.2. 强制数据类型降级
数据类型级别从高到低为:
unsigned long long
long long
unsigned long
long
unsigned int
int
unsigned short
short
unsigned char
char
从高级到低级转换时,超出数值位数的高位部分将被丢弃。另外,降级转换也是是临时的。
3. 测试程序
#include <stdio.h>
#include <string.h>
#define MAX_ERRNO 4095
union err_t {
int i;
long l;
unsigned int ui;
unsigned long ul;
void *p;
char ch[sizeof(unsigned long)];
};
static void test(long val)
{
union err_t err;
memcpy(&err, &val, sizeof(val));
printf("int: %d\n", err.i);
printf("long: %ld\n", err.l);
printf("unsigned int: %u, 0x%08X\n", err.ui, err.ui);
printf("unsigned long: %lu, 0x%08lX\n", err.ul, err.ul);
printf("void *: %p\n", err.p);
}
int main(void)
{
printf("\ntesting: 0\n");
test(0);
printf("\ntesting: -1\n");
test(-1);
printf("\ntesting: -MAX_ERRNO\n");
test(-MAX_ERRNO);
#if 0
printf("\ntesting: -4096\n");
test(-4096);
#endif
return 0;
}
3.1. ARM测试结果
交叉编译后使用QEMU运行查看结果。
arm-linux-gnueabihf-gcc -static -o err_arm err.c
qemu-arm err_arm
testing: 0
int: 0
long: 0
unsigned int: 0, 0x00000000
unsigned long: 0, 0x00000000
void *: (nil)
testing: -1
int: -1
long: -1
unsigned int: 4294967295, 0xFFFFFFFF
unsigned long: 4294967295, 0xFFFFFFFF
void *: 0xffffffff
testing: -MAX_ERRNO
int: -4095
long: -4095
unsigned int: 4294963201, 0xFFFFF001
unsigned long: 4294963201, 0xFFFFF001
void *: 0xfffff001
3.2. ARM64测试结果
交叉编译后使用QEMU运行查看结果。
aarch64-linux-gnu-gcc -static -o err_aarch64 err.c
qemu-aarch64 err_aarch64
testing: 0
int: 0
long: 0
unsigned int: 0, 0x00000000
unsigned long: 0, 0x00000000
void *: (nil)
testing: -1
int: -1
long: -1
unsigned int: 4294967295, 0xFFFFFFFF
unsigned long: 18446744073709551615, 0xFFFFFFFFFFFFFFFF
void *: 0xffffffffffffffff
testing: -MAX_ERRNO
int: -4095
long: -4095
unsigned int: 4294963201, 0xFFFFF001
unsigned long: 18446744073709547521, 0xFFFFFFFFFFFFF001
void *: 0xfffffffffffff001