C语言 关于指针的格式化

来源:互联网 发布:hadoop大数据平台构建 编辑:程序博客网 时间:2024/04/28 16:44
           写这篇文章时由于没有参考过什么权威资料教材,所以有些观点可能是错误的, 不过我本人都是经过大量调试后才写出来的啦..

1. 内存由多个单位为8bit = 1byte的单元组成,每1个单元都配有1个编号,这个就是内存地址。
           计算机的主存储器是内存,而不是硬盘,因为cpu只能直接访问内存...  而cpu访问内存是根据内存的编号来访问的,这个编号就是内存地址,cpu获得内存地址就能直接寻址到对应的内存单位。


2.指针就是内存地址..它们是同一概念
           很多人都讲指针就是指向内存地址的东西, 但是我认为指针就是内存地址..


3.指针变量的概念
           
首先,指针变量是1个变量, 也就是它的值是可以变的。  其次,指针变量是1个特殊的变量, 它存放的值是1个指针(也就是内存地址啦,所以我认为它们是同一样野..


 
4.指针变量所占字节
            
在32位的操作系统中, 系统会使用32个2进制位来表示内存地址, 就是从0000...(32个0)....00 到1111...(32个1)..111, 在这里面的每1个2进制数的都可以表示1个地址, 那么这里面有多少个地址呢? 就是2的32次方 = 4G 个啊。

             而每1个地址对于1个大小为1byte=8bit(1字节)的内存单元, 所以32位系统最多只支持4GB内存的原因就是这个了,因为地址不够用啊.

             而每1个地址都要用32个bit(位来表示), 那么如果要存放这个地址在内存中, 就必须要有32个bit的位置的内存,前面讲到了,每个内存单位的容量是8bit, 所以需要4个内存单元来存放1个内存地址(指针), 也就是说1个指针变量需要 4个地址的内存来存放. 占4个字节。
             即sizeof(p) = 4 了 //p是1个指针
             即对于32位系统来讲, 1个指针变量的长度是4字节
            
通常我们会讲2进制的地址换算成十六进制来表示,
             就是从0x 00000000  到0x FFFFFFFF    
             上面的0x 只不过是十六进制的标志.


            

             在64位的操作系统中,相应地系统会用64个2进制位来表示1个地址, 理论上会有17亿多G个地址, 支持17亿GB内存...当然只是理论上了啊。
               所以64位操作系统中, 1个地址需要64bit内存来存放, 也就是1个指针变量长度是8byte(字节)啦

               由于本屌的系统是64位的, 所以下面的内容和例子都是基于64位系统来说明的。

               下面是一张内存的大概结构图



 
                          

          上面都是一些基本概念, 下面开始才是真正想要说的内容.     



 
5.为什么定义指针变量时要同时指定它的类型。
         
通常我们定义1个指针时要同时指定指针的类型:
          例如
         
char * p;  //定义1个char类型的指针int * p;     //定义1个整性指针



          其实,指针的类型决定了指针获取对应地址内存单元的后续单元个数, 怎么理解呢?
         
         参考上图,下面的指针就是1个char 类型的指针,指向了上面的char类型的变量, 地址是00000000FFFF0001,  而char类型只占1个字节, 所以cpu找到这个地址后就直接获得char类型的值了( 01100001 的值是97 ascii换算后就是字符'a')

         5.1 int类型变量占4字节, 占用4个地址的内存
         但是如果下面的指针要指向上图蓝色的int类型变量呢?
         可以由图中得知,int 类型占4个字节啊, 也就是占住4个地址, 分别是00000000FFFF0002, 00000000FFFF0003,
00000000FFFF0004, 00000000FFFF0005,    但是下面char类型指针只能存放1个地址啊?

         如果我们强制把其中1个int类型的赋给char 类型指针, 就很能报错了, 因为char 类型指针只能访问1个地址内存啊...

         例如我们写个代码如下:
       
  int i = 123;  char * p;  p = &i; // error   char类型指针不能指向int类型的变量



        
       5.2 要指向int类型的变量或常量, 则必须要用int 类型的指针
        
    如果要用指针指向那个Int 类型的变量, 就必须定义1个int 类型的指针
             
  int i = 123;  int * p;  p = &i; //正确


               那是不是指 int 类型指针的长度是char 类型指针的4倍, 能存放4个地址呢?
               当然不是, 其实所有类型的指针长度都一样的, 都只能放1个地址!

                所有当执行 p =&i; 时,  系统实际上是把变量i的头1个字节的地址 赋值给指针p.
                而p去访问他的存放地址时, 首先就会指向int类型i的头1个字节, 而因为它是1个int类型的指针, 所以它会头1个字节开始, 共访问 int类型的长度个数的字节.

           
               而int 类型的长度是4嘛. 所以p指需要知道 int类型的变量的头1个地址, 然后从头1个字节逐个访问4个字节的内存, 就能完全取得int类型的值了.
                
                如下图:

               


             
           
                     
 6.指针可以和整数执行加减运算
          我们经常在代码看到 p++;  p--; 这种语句. 其中p是1个指针啦.
          那么p+1 到底代表什么呢?
          其实假如 n 是 1个整数

          那么 p+ n 其实就是另1个指针,  它 指向P的地址再加上 p指向的数据类型(也就是p的指针类型)的长度 乘于 n.

          也就是假如p 是1个char 类型的指针, 它指向的地址是 0000FFFF00000001 , 那么p + 1 就指向 0000FFFF00000002
           假如p是1个 Int 类型的指针 它的指向的地址是FFFFFFFF00000001, 那么p+1 就指向 FFFFFFFF00000005
       
           也就是
            p + n指向的地址 = p指向的地址  + sizeof(p的类型) * n

           画个图:
        



       假如上面的内存存放的是1个int 类型数组的数据P.   那么P指向的就是头1个数组元素的地址了.
        而数组的变量名字本身是1个指向第1个数组元素的指针
        也就是 *p = p[0]
        而p + 1 指向的是下1个元素的地址 即 *(p+1) =  p[1]

        所以就得出经典公式  *(p+n) = p[n]


8.
不同的类型的指针可以指向同1个地址.
      
大家留意上图, 貌似int 类型 p 和 char 类型的指针q 都指向同1个地址啊..
         写几行代码做个例子.
    
         int i = 97;         int * p = &i; // 定义1个int 类型指针 指向i的地址         int * p2 = &i; //在定义1个指向i的地址的int类型指针 p2, 完全无问题的,只不过浪费8字节去存放这个指针变量了.               现在我想定义1个指向i地址的char 类型指针?         char * q = &i;   //定义1个指向i的地址的 char  类型指针,                                 // 在linux gcc里是可以通过编译的, 但是有warning 信息, 不推荐




         那么正确的写法是什么呢?
         因为i 是1个 int类型变量,   &i就是 这个变量的首个字节地址.  如果要将这个地址传给1个 char类型的变量.
         就要对其进行格式化

         正确写法:
        
     char * q = (char *)&i;      //将int类型变量i的头部地址格式花成 char类型的地址.


        
         这个就是指针的格式化了( 内存地址格式化)  // 这句话只是我自己的理解, 有可能是错的!

         那么这样做有什么效果呢?
         char类型指针 q指向 int类型 i后,  *q 是什么?   

          见下图:
        

 
         因为q指向的地址内容是 01100001 ,就是 97的二进制表示嘛, 而q是1个char 类型的指针. 所以 *q 就是 97的 ascii对应字符 就是小写字母'a'啊.

         下面写个例子程序:

 

执行结果:

 

其实 *q 的真实地址内容是 01100001 这个8个bit 的二进制数.  作为char类型的值就是'a'了,上面程序第个printf 函数只是强制输出为十进制数.

附上gdb的调试信息, 可以看到本例子 p 和 q的指向地址都是 0x7fffffffdebc 啦


 


9.内存插入数据的规律.
     
我们来研究下数据是怎样插入内存的.
           我们知道1个int 类型是占4字节啦 (讲过好多次了)
           而当定义1个i 变量时. 系统就会给它分配 4字节内存.
           而当给i 赋值时, 系统就忘对应内存写数据了
           例如 当执行  i = 97; 时,         系统会将97 换成二进制 1100001, 但这只占7个位. 还有前面32 -7 = 25个位就是0了. 所以
           97 = 00000000 00000000 00000000 01100001

           问题是这些数据怎么插入内存呢,  见上图debug信息: 内存里是
           01100001
           00000000
           00000000
           00000000

           点解貌似反了啊. 不是应该如下吗:
           00000000
           00000000
           00000000
           01100001

        内存每1个位(bit) 都有顺序. 分高低位.
           
到底怎么分呢,
               首先,地址小是地位. 地址大的是高位.
               相同地址的8个bit中, 按照常识, 左边是高位.
              
                而插入数据时,  是从低位开始写入地位的内存, 一直到高位.
               例如97 这个整数, 
                00000000  00000000   00000000 01100001
                从左到右是高到低,  插入内存时就下图所示了:

 

              假如我定义1个int类型变量
            
  int i = 24930;

 
              接下来定义1个char 类型指针指向它.
            
              char *q = &i              printf("*q is %c\n", *q)        //*q是多少呢?              printf("*(q+1) is %c\n", *(q+1))    //*(q+1) 又是多少呢?



              步骤还是先把24930 这个化为二进制数 110000101100010, 然后补齐到32bit (4字节)
              就是 00000000  00000000 0110000101100010
              那么p 就是指向最低位(头部地址)的字节   01100010  ,化成10进制数是98 啊, ascii码就是小写字母'b'了
              而p+1 指向p的地址加上 1乘于 char类型的长度.  就是下1个地址啊. 就是指向 01100001 ascii码就是小写字母'a'了
             
            下面就是这个例子程序


结果:



 
10.内存如何存入int类型的负数
    
继续用上面那个例子, 假如我定义1个int 类型的负数
       int i = -24930;
       那么内存里的数值是什么呢?

       我们可以分析 24930的二进制是
       00000000  00000000 0110000101100010
    

    本文第1张插图已经提过int 类型最高位是用来代表正负的. 所以加上正负位:
    
10000000  00000000 0110000101100010

   
那么内存里的数据就是上面那个二进制数吗?

       不是的因为实际上负数是按补码形式来存储的,  而负数的补码是取反再+1
      

10000000  00000000 01100001 01100010      
1
1111111    11111111  1001111010011101
       先取反(符号位忽略)
11111111    11111111  10011110 10011110       再+1

  
上面第3行的就是-24930 在内存中的数据了

gdb的信息

原创粉丝点击