C指针转换

类型转换是一种基本操作,跟指针结合使用时很有用。转换指针对我们大有帮助,原因包括:

  • 访问有特殊目的的地址;
  • 分配一个地址来表示端口;
  • 判断机器的字节序。

我们也会处理一个跟别名、强别名和restrict中的类型转换紧密相关的主题。

注意 机器的字节序一般是指数据类型内部的字节顺序。两种常见的字节序是小字节序和大字节序。小字节序是指将低位字节存储在低地址中,而大字节序是指将高位字节存储在低地址中。

我们可以把整数转换成整数指针,如下所示:

int num = 8;
int *pi = (int*)num;

不过,一般来说这不是好实践,因为它允许你访问任意地址,包括系统不允许程序访问的位置。图8-1说明了这一点,地址8不在应用程序的地址空间内,如果解引指针,一般就会导致应用程序终止。

enter image description here

图8-1:把整数转换成非法地址

有些情况下,比如我们需要寻址内存地址0,就可能需要把指针转换成整数,然后再转换回指针,这在老式的系统上比较常见,其指针长度和整数长度相同。不过,有时候这样不能正常工作。下面说明了这种方法,输出跟实现相关:

pi = #
printf("Before: %p\n",pi);
int tmp = (int)pi;
pi = (int*)tmp;
printf("After: %p\n",pi);

把指针转换为整数再转换回指针从来就不是什么好办法,如果确实需要这么做,考虑用联合体,别名、强别名和restrict会讨论这个主题。

记住在指针与整数之间来回转换和在指针与void指针之间来回转换不同,null指针和void指针中的“void指针”部分有说明。

注意 有时候容易将句柄和指针搞混。句柄是系统资源的引用,对资源的访问通过句柄实现。不过,句柄一般不提供对资源的直接访问,指针则包含了资源的地址。

访问特殊用途的地址

访问特殊用途的地址的需求一般发生在嵌入式系统上,嵌入式系统对应用程序的介入很少。比如说,在有些底层操作系统内核中,PC的显存地址是0xB8000,这个地址装的是字符模式下显示的第一行第一列的字符,我们可以把这个地址赋给某个指针,然后把某个字符赋给这个地址,代码如下所示。图8-2显示了内存布局。

#define VIDEO_BASE 0xB8000
int *video = (int *) VIDEO_BASE;
*video = 'A';

enter image description here

图8-2:在PC上寻址显存

在适当情况下,可以读取这个地址的内容,不过一般不会对显存地址这么做。

当你需要寻址地址0的内存时,有时候编译器会把它当做NULL指针值。底层内核程序通常需要访问地址0,有几种技术可以处理这种情况:

  • 把指针置为0(不一定能工作);
  • 把整数置为0,再把这个整数转换为指针;
  • 别名、强别名和restrict中提到的联合体;
  • memset函数把指针置为0。

下面是一个使用memset函数的例子,这里将ptr引用的内存置为0:

memset((void*)&ptr, 0, sizeof(ptr));

在需要寻址0地址内存的系统上,厂商一般会有解决问题的办法。

访问端口

端口既是硬件概念,也是软件概念。服务器用软件端口指明它们要接收发给这台机器的某类消息。硬件端口通常是一个连接到外部设备的物理输入输出系统组件。程序通过读写硬件端口可以处理信息和命令。

访问端口的软件一般是操作系统的一部分,下面说明如何用指针访问端口:

#define PORT 0xB0000000
unsigned int volatile * const port = (unsigned int *) PORT;

机器用十六进制地址表示端口,将数据作为无符号整数处理。volatile关键字修饰符表示可以在程序以外改变变量。比如说,外部设备可能会向端口写入数据,且可以独立于计算机的处理器执行这个写操作。出于优化目的,编译器有时候会临时使用缓存或是寄存器来持有内存中的值,如果外部的操作修改了这个内存位置,改动并不能反映到缓存或寄存器中。

volatile关键字可以阻止运行时系统使用寄存器暂存端口值,每次访问端口都需要系统读写端口,而不是从寄存器中读取一个可能已经过期的值。我们不应该把所有变量都声明为volatile,因为这样会阻碍编译器进行所有类型的优化。

之后应用程序可以通过解引端口指针来读写端口,如下所示。内存布局见图8-3,可以通过0xB0000000处的内存读写外部设备:

*port = 0x0BF4; // 写入端口
value = *port; // 从端口读取

enter image description here

图8-3:访问端口

警告 用非volatile变量访问volatile内存不是个好主意,这么做会导致未定义的行为。

用DMA访问内存

直接内存访问(Direct Memory Access,DMA)是一种辅助系统在内存和某些设备间传输数据的底层操作,它不属于ANSI C规范,但是操作系统通常提供对这种操作的支持。DMA操作一般与CPU并行进行,这样可以将CPU解放出来执行其他任务,从而得到更好的性能。

程序员先调用DMA函数,然后等待操作完成。通常,程序员会提供一个回调函数,当操作完成后,操作系统会调用回调函数,回调函数由函数指针指定,8.3.2节中会深入讨论。

判断机器的字节序

我们可以使用类型转换操作来判断架构的字节序。字节序是指内存单元中字节的顺序,字节序一般分为小字节序大字节序,比如说,采用小字节序表示整数的4个字节中的低地址用来存储整数的低位。

在下例中, 我们把整数的地址从指针转换为char,然后打印各个字节:

int num = 0x12345678;
char* pc = (char*) #
for (int i = 0; i < 4; i++) {
    printf("%p: %02x \n", pc, (unsigned char) *pc++);
}

这个代码片段在Intel PC上的输出如下,反映了这是小字节序架构。图8-4说明了这些值在内存中如何分配。

100: 78
101: 56
102: 34
103: 12

enter image description here

图8-4:字节序示例

赞(2)

评论 抢沙发

评论前必须登录!

 

C指针