当前位置: C语言 -- 基础 -- 转换

转换(三)

二、其它操作数

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类型。




主要参考资料:

1、ISO/IEC 9899:2024

2、ISO/IEC 9899:2018

3、en.cppreference.com : Implicit conversions

4、gnu.org : Type Conversions

5、clanguagebasics.com : C Type Conversion - Implicit & Explicit Type Conversion in C

6、learn.microsoft.com : 常用算术转换