C语言深处

    返回首页    发表留言
本文作者:李德强
          第一节 类型转换
 
 

一、指针与地址

        学习指针就要知道指针到底是什么?我们知道内存是一系列连续的byte字节,每一个byte都有一个唯一的编号,叫内存地址(物理地址)。指针也是一个普通变量,只不过这个变量用于存放一个内存地址。下面我们来举例说明指针的本质:

int i = 7;
int *p = &i;

        假设变量i在内存中的地址为0x2000,变量p在内存中的地址为0x2004,它们在内存中的存储状态如下:


 

        也就是说变量i的地址为0x2000,而指针变量p指向了这个地址,实际上就是p这个变量的值是0x2000这个数值。在32位架构下所有指针都占4个byte。

        来看下面的例子:

#include <stdio.h>

int main(int argc, char **args)
{
        int i = 7;
        unsigned int addr = (unsigned int) &i;
        int *p = (int *) addr;
        printf("0x%x\n%d\n", p, *p);

        return 0;
}

        运行结果:

0xffc7f654
7

        其实指针就是一个特殊的占用4字节的无符号整型变量,它与unsigned int唯一的区别就是指针的值是一个内存地址而已。我们可以通过&i来得到i所在的内存地址,这个地址就是一个无符号整数,再把这个整数做为指针类型赋值给p是合法的,没有任何问题(在32位架构下)。

 

二、类型转换

        对于所有的指针都是用于存放内存地址,都占用4个byte。那么编译器是如何知道这些地址中存放的什么类型的变量呢?答案是通过指针的类型。假设一个指针变量的地址为0x2000,如果这是一个char型指针,那么编译器则认为这个地址中存放的是一个char型变量,占用1个byte;如果这是一个int型指针,那么编译器则认为这个地址中存放的是一个int型变量,占用4个byte;如果这是一个float型指针,那么编译器则认为这个地址中存放的是一个float型变量,占用4个byte;其它类型的指针变量也都是一样的道理。如下图:


 

       下面我们来看一下关于指针的强制转换问题

#include <stdio.h>

int main(int argc, char **args)
{
        char  *p1;
        short *p2;
        short *p3;
        int   *p4;
        char   i = 0x11;
        char   j = 0x22;
        char   k = 0x33;
        char   l = 0x44;

        p1 = &i;
        printf("0x%x\n", *p1);

        p2 = (short *) &j;
        printf("0x%x\n", *p2);

        p3 = (short *) &k;
        printf("0x%x\n", *p3);

        p4 = (int *) &l;
        printf("0x%x\n", *p4);

        return 0;
}

       运行结果:

0x11
0x1122
0x2233
0x11223344

        来看一下p1、p2、p3、p4、i、j、k、l这些变量在内存中的地址和内容:


 

        可以看到当地址0x2002赋值给p2时,因为p2是一个short型指针,因此在对p2做解引用时,编译器认为在这个地址中存放的是一个short型变量,占2个byte,所以*p2的值为0x1122;同样的道理*p3的值为0x2233;而p4是一个int型指针,因此在对p4做解引用时,编译器认为在这个地址中存放的是一个int型变量,占4个byte,所以*p4的值为0x11223344。

        再来看一个强制转换的例子:

#include <stdio.h>

int main(int argc, char **args)
{
        int i = -1024005767;
        printf("%d\n", i);

        float *p = (float *)&i;
        printf("%f\n", *p);

        return 0;
}

        运行结果:

-1024005767
-123.456001

        是不是很奇妙?其实跟上面所说的原理没有任何区别,当指针p在解引用时编译器认为这是一个指向float浮点变量的地址,于是编译器就会按float的计算方式来解析这个地址的内容。-1024005767的16进制值为0xC2F6E979,这个数值用计算浮点数的公式来解析所得到的值就是-123.456001。所以,这个运行结果是正确的。

        在浮点数章节中我们知道float和double类型的变量是不能做位运算的,但是我们可以通过指针强制转换的方式来间接的对其做位运算,虽然这个作法并不合理,我们也不提倡,但是我们要明白,在实际编程时可以这么做:

#include <stdio.h>

int main(int argc, char **args)
{
        float i =-0.123456;
        printf("%f\n", i);

        int *p = (int *) &i;
        *p <<= 1;
        printf("%f\n", i);

        return 0;
}

        运行结果:

-0.123456
2592781549273207636974872920896569344.000000

        结果是一个非预期的浮点数,因为在做左位移运算时,浮点数的符号位、阶码和尾数都被修改了,这不是一个好的程序,这样的程序在实际中无法确定结果的正确性,所以对浮点数做位运算是很有风险的。

        再来看一个例子:

#include <stdio.h>
#include <string.h>

int main(int argc, char **args)
{
        //0x61 == 97 == 'a'
        int i = 0x61;             
        char *p = (char *)&i;
        printf("%c\n", *p);
        printf("%d\n", strlen(p));
}

        大尾机运行结果:

0

        小尾机运行结果:

a
1

        来看一下这个例子中变量i在内存中的结构:


 

        字符型指针char *p的值为0x2000,编译器在对p解引用时认为0x2000处是一个char型变量占1个byte,在大尾机中0x2000处存放的是0x00,而在小尾机中存放的则是0x61。所以在大尾机中运行上面例子,结果为“空”,长度为0;而在小尾机中运行结果为‘a’,长度为1。

    返回首页    返回顶部
  看不清?点击刷新

 

  Copyright © 2015-2018 问渠网 辽ICP备15013245号