C指针与内存

  指针是C / C++ 中重要的构造类型,指针赋予了C / C++程序直接访问和修改内存的能力。C / C++的许多重要应用,如编译、OS、嵌入式开发都依赖于这种能力。

  冯诺依曼体系的计算机内存存储指令和数据,我们可以将其抽象为指令区和数据区(当然实际情况要复杂得多)。数据区中包含栈(stack)和堆(heap)区,栈区的数据由编译器管理而堆区数据则由程序员管理。

  由于指令同样存在于内存中,那么函数在内存中也会拥有地址(指针)。 函数指针相对于函数名来说 可以在运行期动态地选择调用的函数,这一特性是C++实现动态多态性的重要基础。

一、 指针的基本用法

  指针是一种构造类型,所有的基本类型和struct等都有自己的指针。指针包含首地址(即指针的值)和指向对象的类型两部分信息,所以指针并不严格等价于地址。

  不要使用未初始化的指针否则将导致严重的运行时错误,所幸操作系统的内存管理可以保证操作系统自身和其它进程的稳定,但你的程序肯定会异常退出了。指针的类型是重要的,指向struct的指针初始化后可以使用ptr -> member表达式来访问某一个成员,但是未经初始化或void 指针将导致错误或警告。

  在《C编程基础》中提到,方括号([]),指针(*)这些符号在声明语句和执行语句中的含义有所不同,但依旧具有运算符的一些特性。

即指针(*),方括号([])以及函数调用的(参数表)在声明语句中 可以认为是将一个标识符标记为特殊类型的标志,当同一个声明语句中存在多个标志时,它们按照表达式求解的顺序决定声明的类型:

  (1)基本声明:

     int *p; 声明指针p(注意不是*p),*p表示其指向的数据。

     int p[M]; 声明长为M的一维数组p。

     int p[M][M]; 声明M×M二维数组p。

  (2)复合声明:

     int **p 声明p为二重指针(不是**p),则 *p为一重指针,**p为数据对象。

     int *p[M] 声明指针数组,初等运算符从右向左结合首先声明一个数组,然后声明数组元素类型为指针。

     int *fun(...) 声明返回指针的函数,从右向左首先声明fun为函数,然后声明返回值类型为指针。

     int (*ptr) (...) 声明ptr为函数指针,从右向左首先声明这是一个函数,(*ptr)声明这是一个函数指针。使用函数指针时只需以(*ptr)代替函数名即可,例:

   qsort库函数使用函数指针作谓词函数

#include<stdio.h>

#include<stdlib.h>

int tell(const void *a,const void *b) {

   int x, y;

   x = *(int *)a;

   y = *(int *)b;

   return x - y;

}

int main(void) {

   ] = {  };

   int (*tp) (const void *a, void *b);

   tp = tell;

   scanf("%d", &m);

   ; i < m; i++) {

     scanf("%d", &a[i]);

   }

   qsort (a, m,sizeof(int ),tp);

   ;i<m;i++) {

printf("%d ",a[i]);

   }

   printf("\n");

   ;

}

   指针作为函数参数则可以实现传址调用,下面给出一个最简单的交换两个数的程序:

#include<stdio.h>

   int swap(int *pa, int *pb) {
             int t;

  t = *pa; // *pa is "a" itself

  *pa = *pb;

  *pb = t;

  ;

         }

   int main(void) {

  int a,b;

  scanf("%d %d",&a,&b);

  swap(&a,&b); // using &a as a ptr to "a"

  printf("%d %d\n",a,b);

  ;

   } 

·常指针

    const int * ptr;  int const * ptr; 指向常对象的指针

    int * const ptr = &a; 指向不能改变的指针,必须初始化

    const int * const ptr = &a;  int const * const ptr;

   指向与对象均不能改变的指针;

   以*为标志,靠近基本类型的为指向常对象,靠近指针名为指向不变。

二、 数组及其存储

  1.一维数组

  上文已经说明数组的声明方式:

    (1)声明一个长度为8的int数组arr ];

    (2)数组的长度必须为常正整数,不能是const对象【const int n = 8;int arr[n];】;可以是枚举元素但不能是枚举对象,通常使用字面值或宏来作为长度 #define N 10 ;

    (3)数组下标从0开始(不是1);

    (4)方括号([])可以访问数组的某个成员, arr[i];arr[]; 。

  在函数内定义的数组不会自动初始化,在函数外定义的数组将自动初始化为0(或等价值)。在函数内定义数组同时初始化时,未初始化变量将自动初始为0值 ] = {,,}; 常用这个特性将数组全部初始化为0值, ] = {}; 。将所有元素初始化时不需要指定数组长度,即 ,}; 与 ] = {,} 等价。

  2.高维数组

  我们可以将高维数组理解为数组的数组,即高维数组的元素是低一维的数组。因为数组就名就是其首地址,那么,高维数组的元素是低维数组的首地址。以二维数组为例,利用a[i]<=>*(a+i)逐级展开

        a[i][j] <=> *(a[i]+j) <=> *(*(a+i)+j)

  i的单位为一维数组的长度,j的单位为基本类型的长度。推导时,将[]展开即可,加一个&就是少一个*。

        *(a+i)+j <=> a[i]+j <=> &a[i][j]

  高维数组的初始化有两种形式。首先将其元素理解为数组,每一个数组的初值用一个花括号括起,把它们当做一个元素用另一个大括号括起。高维数组在内存中是线性存储的,可以根据它在内存中的存储顺序当做一维数组进行初始化。这两种方式下,没有被赋值的元素均被自动赋0。在对所有元素赋值时,第一维的长度可以省略由编译系统求出,其它维数不可省略。

  3.

  数组是一段连续的内存空间,数组名可以认为是常指针(不允许更改指向)。从数组实现来看,下标从0开始是非常自然的。

  C对下标的处理方法的处理方法就是将它转化为地址,a[i]与*(a+i)无条件等价。

三、动态内存管理

  1.分配内存(memory alloc):

     void *malloc(unsigned int size); (常用)

    (1) 在内存中分配一个大小为size的连续空间,参数size为无符号整型(不允许为负数)。函数的返回值是所分配区域第一个字节的地址。如果函数不能成功地执行(如内存空间不足)则返回空指针(NULL)。

    (2) void指针类型不能理解为指向任何类型,而应理解为指向不确定类型的数据的指针。应用指派运算符(强制转换)将void指针变为具体的类型指针,size参数应用sizeof()运算符求得以避免错误,如结构体的大小不严格等于所有成员大小之和并提高程序可移植性。例如:

     int * ptr = (int *)malloc( sizeof(int)* N );

    声明ptr指向一段长度为N连续int型空间(实际上是一个长度为N的int数组)。

  2.分配连续空间:

     void *calloc(unsigned n,unsigned size);

    在动态存储区分配n个长度为size的连续空间,可以用来保存一个数组。

  3.释放内存(free):

     void free(void *p);

    释放指针变量p所指向的动态空间,无返回值。例如:  free(ptr);

  4.重新分配内存(realloc):

     void *realloc(void *p,unsigned int size);

    将p指向的动态空间大小改变为size。

C指针与内存的更多相关文章

  1. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

  2. C 语言中的指针和内存泄漏

    引言对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧 ...

  3. C语言中的指针和内存泄漏

    引言 对于任何使用C语言的人,如果问他们C语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧,但是 ...

  4. 【ZZ】C 语言中的指针和内存泄漏 & 编写高效的C程序与C代码优化

    C 语言中的指针和内存泄漏 http://www.ibm.com/developerworks/cn/aix/library/au-toughgame/ 本文讨论了几种在使用动态内存分配时可以避免的陷 ...

  5. Delphi 的内存操作函数(2): 给数组指针分配内存

    静态数组, 在声明时就分配好内存了, 譬如: var   arr1: ..] of Char;   arr2: ..] of Integer; begin   ShowMessageFmt('数组大小 ...

  6. [Windows] [VS] [C] [取得指针所指内存的二进制形式字符]

    // 取得指针所指内存的十六进制形式字符串,size指定字节长度#define Mem_toString(address, size) _Mem_toString((PBYTE)address, si ...

  7. [Windows] [VS] [C] [取得指针所指内存的十六进制形式字符串]

    接口定义如下: #include <Windows.h> // 取得指针所指内存的十六进制形式字符串,size指定字节长度 #define Mem_toString(address, si ...

  8. [C]C语言中的指针和内存泄漏几种情况

    引言 原文地址:http://www.cnblogs.com/archimedes/p/c-point-memory-leak.html,转载请注明源地址. 对于任何使用C语言的人,如果问他们C语言的 ...

  9. C语言中的指针和内存泄漏几种情况

    引言 原文地址:http://www.cnblogs.com/archimedes/p/c-point-memory-leak.html,转载请注明源地址. 对于任何使用C语言的人,如果问他们C语言的 ...

随机推荐

  1. poj2481

    题意:给定一些线段(s, e),起点为s,终点为e,求每一段线段被多少线段包含(不包括相等) 思路:很明显的树状数组题目..但是做的时候想了挺久..(下面的x为线段起点, y为线段终点) 做法1:先对 ...

  2. 使用JAVA API 解析ORC File

    使用JAVA API 解析ORC File orc File 的解析过程中,使用FileInputFormat的getSplits(conf, 1)函数, 然后使用 RecordReaderreade ...

  3. pyqt5 Button.click 报错:argument 1 has unexpected type 'NoneType'

    如上所示,在括号中,添加‘lambda:’,就可以成功运行,不知道为啥. 参考:https://blog.csdn.net/flhsxyz/article/details/79220936?utm_s ...

  4. POJ 2442(优先队列 k路归并 堆)

    Description Given m sequences, each contains n non-negative integer. Now we may select one number fr ...

  5. [vuejs] 终端npm run dev 不能自动打开浏览器运行项目解决办法

    终端执行: npm run dev 出现: I Your application is running here: http://localhost:8080 但并没有打开浏览器运行项目 解决办法: ...

  6. yarn 学习 小记

    官网:https://yarnpkg.com/zh-Hans/docs/installing-dependencies 简介:包管理工具,和npm类似主要特点:快速.安全.可靠 快速:本地安装包后,会 ...

  7. day 72 crm(9) 客户关系系统,整体实现,以及自定制路由内容,客户关系梳理

    前情提要: crm(9)  ,利用stark 组件和,rbac组键的使用,   明确观点: 一:客户关系需求明确 1:客户关系系统,主要是,本业务逻辑主要是 销售就客户关系的业务逻辑, 二: 创建ap ...

  8. VS2013 编辑器

    1. VS -> 本地Git -> Github 1. 右键单击解决方案,选择“将解决方案添加到源代码管理器”,选择Git 2. 切换到团队资源管理器([菜单]视图->团队资源管理器 ...

  9. swiper4-vue 不使用loop,由最后一张跳到第一张

    <template> <div class="swiper-box"> <div class="swiper-container" ...

  10. odoo开发笔记 -- 用户配置界面如何增加模块访问权限

    在odoo设置界面,点击用户,进入用户配置界面,会看到: 访问权 | 个人资料菜单 在访问权 page菜单界面,可以看到系统预制的一些模块都会显示在这里, 那么,我们自己开发的模块如何显示在这块呢,从 ...