转换(三)
二、其它操作数
1、左值、数组和函数指示符
左值(lvalue)是可以指定对象的表达式(void类型除外);如果左值评估时没有指定对象,其行为是未定义的。左值最初来源自赋值表达式E1 = E2,其中左操作数E1要求必须是可修改左值。左值可能更适合视为是表示对象的定位值(locator value);在ISO/IEC 9899:2024标准中表达式的值有时称为右值(rvalue)。
当一个对象具有特定类型时,该类型由指定该对象的左值指定。可修改左值(modifiable lvalue)不能是数组类型,不能是不完整类型,也不能使用const类型限定符限定;如果可修改左值是结构或者联合,其成员不能使用const类型限定符限定(包括递归包含的所有聚合或者联合的任何成员或者元素)。
int x = 5; //x是左值,也是可修改左值。 const int y = 8; //y是左值,但不是可修改左值。
除非是sizeof运算符、typeof运算符、一元运算符&、++运算符、--运算符的操作数,或者.运算符、赋值运算符的左操作数,非数组类型左值将转换成存储在指定对象中的值(并且不再是左值。),这称为左值转换(lvalue conversion)。
如果左值具有限定类型,则转换结果值具有左值类型的非限定版本;如果左值具有原子类型,则转换结果值具有左值类型的非原子版本;否则转换结果值具有左值的类型。
#define TYPE(x) _Generic((x), \ int: "int type", \ const int: "const int type", \ int *: "pointer to int type", \ const int *: "pointer to const int type", \ default: "unknown type" \ ) ... const int ci = 5; printf("%s\n", TYPE(ci)); //ci发生左值转换,输出int type。 printf("%s\n", TYPE(&ci)); //ci未发生左值转换,输出pointer to const int type。
如果左值具有不完整类型,并且不是数组类型,其行为是未定义的。如果左值指定了一个具有自动存储期限的对象,该对象使用register存储类说明符声明,并且未初始化,其行为是未定义的。
数组类型表达式会转换成对应的指针类型,以下情况除外:
-- 数组类型表达式是sizeof运算符的操作数。
-- 数组类型表达式是typeof运算符的操作数。
-- 数组类型表达式是一元运算符&的操作数。
-- 数组类型表达式是用于初始化数组的字符串字面量(string literal)。
转换得到的指针指向数组对象的初始元素,并且不是左值。
如果数组对象存在register存储类说明符,其行为是未定义的。
#define TYPE(x) _Generic((x), \ int: "int", \ int *: "pointer to int", \ int (*)[3]: "pointer to an array of 3 ints", \ default: "unknown type" \ ) ... int arr[3]; printf("%s\n", TYPE(arr)); //输出pointer to int。 printf("%s\n", TYPE(&arr)); //输出pointer to an array of 3 ints。
函数指示符(function designator)是具有函数类型的表达式。除非作为sizeof运算符、typeof运算符或者一元运算符&的操作数(注:sizeof运算符的操作数不能是具有函数类型的表达式。),否则函数指示符转换成指向函数返回类型的指针。
#define TYPE(x) _Generic((x), \ int *: "pointer to int", \ int (*)(int): "pointer to int (*)(int)", \ default: "unknown type" \ ) int func(int a) { return a; } ... printf("%s\n", TYPE(func)); //输出pointer to int (*)(int)。 printf("%s\n", TYPE(&func)); //输出pointer to int (*)(int)。 printf("%d\n", func(5)); //合法,输出5。 printf("%d\n", (*func)(8)); //合法,输出8。
func(5)和(*func)(8)两种函数调用方式都是合法的。
2、void类型、指针类型和nullptr_t类型
不能以任何方式使用void类型表达式的值,也不能对void类型表达式进行隐式或者显式类型转换(转换成void类型除外。)。如果其它类型的表达式评估为void类型表达式,其值或者指示符将被丢弃。评估void类型表达式主要是因为其副作用。
int x, y; printf("%d\n", (x=5, y=8)); //输出8。 printf("%d\n", x); //输出5。 printf("%d\n", y); //输出8。
逗号(,)运算符的左操作数评估为void类型表达式,右操作数是表达式的值;表达式(x=5, y=8)中左操作数x=5的作用是给变量x赋值。
void类型指针是通用指针,void类型指针可以转换成指向对象类型的指针,指向对象类型的指针也可以转换成void类型指针。指向对象类型的指针转换成void类型指针,再转换成指向对象类型的指针,结果与原始指针相等。
int x; int *temp = &x; int *ptr = &x; if( (int *)((void *)ptr) == temp) puts("equal"); //输出equal。 else puts("not equal");
指向非限定类型的指针可以转换成指向限定类型的指针,转换得到的指针和原始指针的值相等。
int x; int *p = &x; const int *cp; cp = p; //合法。 p = cp; //非法。
转换为void *类型的、值为0的整数常量表达式或者预定义常量nullptr称为空指针常量(null pointer constant)。如果将空指针常量或者nullptr_t类型值转换成指针类型,得到的指针称为空指针(null pointer)。空指针与任何对象指针、函数指针都不相等。将空指针转换成另一种指针类型时,会得到该类型的空指针。任意两个空指针应相等。
int *ip = 0; //ip是空指针。 float *fp = NULL; //fp是空指针。 printf("%p\n", ip); //输出0x0。 printf("%p\n", fp); //输出0x0。
整数可以转换成任何指针类型;除非整数在前面已经指定,否则结果将由实现定义,转换结果可能没有正确对齐,也可能没有指向引用类型的实体,存储到对象中时可能会生成不确定表示。
任何指针类型都可以转换为整数类型。除非指针在前面另有说明,否则结果将由实现定义。如果结果不能使用整数类型表示,其行为是未定义的。转换结果不需要在任何整数类型的值域范围内。
int x; int *ptr = &x; int *temp = NULL; long long ll = (long long)ptr; //指针转换成整数。 temp = (int *)ll; //整数转换成指针。 *temp = 10; printf("%p\n", ptr); //输出0xffffcc24。 printf("%#llx\n", ll); //输出0xffffcc24。 printf("x = %d\n", x); //输出x = 10。
一般而言正确对齐的概念是可传递的:如果A类型指针与B类型指针正确对齐,B类型指针与C类型指针正确对齐,则A类型指针与C类型指针正确对齐。
对象类型指针可以转换成其它不同对象类型的指针;如果结果指针对于引用的类型不能正确对齐,其行为是未定义的;否则再次转换成原类型指针时,结果应与原始指针相等。
指向对象的指针转换成指向字符类型的指针时,结果指针指向对象的最低寻址字节(the lowest addressed byte);结果指针连续加1,直至对象大小,可以遍历指向对象的剩余字节。
int number = -2; size_t size = sizeof(int); unsigned char *ptr = NULL; ptr = (unsigned char *)(&number); //转换指针类型。 /*遍历对象number的字节。*/ for(size_t u=0; u<size; u++) printf("%#X ", *(ptr+size-1-u));
函数类型指针可以转换成另一种函数类型指针,然后再转换成原函数类型指针;结果指针应与原始指针相等:如果使用转换后的指针调用类型与引用类型不兼容的函数,其行为是未定义的。
nullptr_t类型可以转换为void类型、bool类型或者指针类型,转换结果分别是void类型表达式、false或者空指针值。空指针常量可以转换为nullptr_t类型。
主要参考资料:
3、en.cppreference.com : Implicit conversions
5、clanguagebasics.com : C Type Conversion - Implicit & Explicit Type Conversion in C
6、learn.microsoft.com : 常用算术转换