详细总结C语言指针

前言

好久没更了,一是忙工作,二是忙着玩了,为了更好的学习与复习,继续开始保持更新博客的习惯,欢迎大家指点与交流。

新工作接手老员工的代码,把自己搞得痛不欲生同时也深刻的了解到自己之前的代码是造了多大的孽。全局变量漫天飞,变量名字随便来,没有任何模块化代码的概念。要写好C代码,不仅仅需要一份编程规范,更需要的是如何将代码模块化,这里仔细讲讲C语言的灵魂所在——指针。

一、概念

1.1、什么是指针?

 “指针就是地址”,相信大家都知道这个答案。然而能在代码中灵活运用指针的人并不是绝大多数。

相信对于初学者或者是对C语言指针掌握得不是很好的“道友们”来说,同一个需求一样能实现得很好,但是你的代码可能只有你自己可以进行维护,并且出了BUG,没人能帮你,只能自己“渡劫”。毕竟自己造的孽,只能自己来承受。
        指针被誉为C语言的精髓,不能真正掌握指针,可以直白的说,你不懂C。并且随着时间的推移,你对C语言的掌握程度可能还不如刚学习C语言的情况。日渐熟练的偷懒,最终的结果就是让代码中的的全局变量,飞得更婀娜多姿。所有函数都是void func(void)。

1.2、为什么说“指针就是地址”?

 在这里我们先说一下另一个东西——内存,计算机的内存是一块用于存储数据的空间,由一组连续的存储单元组成。下图所示为1K内存的简单示意图。

上图中byte(字节),内存寻址的最小单元,图左侧的数据0x00000000~0x000003FF,每一个byte都有唯一对应的地址编码信息,也就是地址。

我们可以想象一下,一排储物柜就是一块连续的内存,最小的内存单元就是每个柜子对应每个byte,每个柜子的作用都是用来存放东西的且容量相同,储物柜上面的编号就是他们的地址信息,并且每一个储物柜都只有唯一的一个编号。C语言里,变量存放在内存中,CPU通过内存寻址对存储在内存中的数据对象进行定位。指针变量保存的就是这个地址信息。所以说“指针就是地址”

 这里说到了内存数据存储,那顺便说一下字节序的问题。 什么是字节序,简单的来说就是指超过一个字节的数据类型在内存中存储的顺序,字节序主要分为大端序和小端序两种字节序:

大端序:高位字节数据存放在低地址内存处,低位字节数据存放在高地址处
       小端绪:低位字节数据存放在高地址内存处,高位字节数据存放在低地址处

我们可以用代码检测一下自己电脑CPU的字节序。

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

typedef union 
{
	char c;
	int n;
}_uTmpData_t;

/**
* @brief:检测CPU的字节序
* @retval:小端序返回TRUE,
*			大端序返回FLASE
*/
bool check_cpu_byte_order(void)
{
	_uTmpData_t _uTmpData;
	_uTmpData.n = 1;

	return (1 == _uTmpData.c);
}

int  main(int argc, const char argv[])
{
	if (check_cpu_byte_order())
	{
		printf("CPU的字节序为小端序\r\n");
	}
	else
	{
		printf("CPU的字节序为大端序\r\n");
	}
	
	return 0;
}

1.3、为什么要使用指针? 

在C语言中,指针的使用非常广泛,通过指针,可以提高代码的灵活性、后期的可维护性、程序的编译效率和执行速度,并且使得程序更加简洁。具体实现方法如下:
      1)被调函数可以返回除正常返回值以外的更多数据
      2)实现动态内存分配
      3)表示和实现各种复杂数据结构
      4)直接操作内存,提高代码效率
      5)将函数作为参数传递到被调函数当中

 

二、应用

2.1、如何声明并初始化一个指针?

 指针也是一个变量,指针的声明方式与其他数据类型变量的声明方法类似:

#include <stdio.h>

int  main(int argc, const char argv[])
{
	int a = 10;
	int *_pn = &a;                            // 定义了一个指针变量p,并且指向了变量a

	printf("address of a = %#x\r\n", &a);
	printf("address of _pn  point to memory = %#x\r\n", _pn);
	printf("address of _pn  = %#x\r\n", &_pn);
	printf("value of _pn  point to memory = %d\r\n", *_pn);
	return 0;
}
结果如下所示:
    address of a = 0xbfd816c8
    address of p point to memory = 0xbfd816c8
    address of p = 0xbfd816cc
    value of p point to memory = 10

*:在算术运算的时候该符号为“乘”,在指针相关操作时则不同,在定变量时使用 (int *p)  代表定义的是指针变量,在指针变量前使用代表取值(*p + 1)
      &:取地址符号,获取变量的地址

指针变量长度:
    size of void   * = 4 
    size of char   * = 4 
    size of short  * = 4 
    size of int    * = 4 
    size of float  * = 4 
    size of double * = 4

在C语言中不同的数据类型的长度不同,但是不同类型的指针长度为什么都是4字节?
       在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。

既然所有类型指针的长度都是4个字节,与对应的数据类型不一致,那么定义不同类型的指针有什么用?

指针就是地址,他存储的是变量存储在内存的地址信息,不同类型的指针是给编译器看的,编译器会根据指针类型从内存中按照指定格式进行数据或者。

#include <stdio.h>

int  main(int argc, const char argv[])
{
	char tempArray[8] = {1, 2, 3, 4, 5, 6, 7, 8};
	char  *_pc = (char  *)tempArray;
	short *_ps = (short *)tempArray;
	int   *_pn = (int   *)tempArray;


	printf("value of _pc point to memory = %#x \r\n", *_pc);
	printf("value of _ps point to memory = %#x \r\n", *_ps);
	printf("value of _pn point to memory = %#x \r\n", *_pn);
	return 0;
}
结果如下:
    value of _pc point to memory = 0x1 
    value of _ps point to memory = 0x201 
    value of _pn point to memory = 0x4030201 

通过上文的代码与图片可以看到,不同类型的指针变量从内存获取数据的时候,会根据指针类型获取到不同长度的数据,再按照字节序将获取到数据按照对应格式拼接到一起。

因此,不同类型的指针变量决定的是获取的数据长度。大家在学习串口的时候,应该都写过字节拼接函数,发送端将short或者int型数据等等拆分成一个个byte通过串口发送出去,在接收端则是将接收到的多个byte数据,按照发送端拆分的逆序进行拼接赋值,这里我们学会指针则指直接可以可以利用指针,让CPU帮我们做字节拼接的工作。

#include <stdio.h>

int  main(int argc, const char argv[])
{
	char _arr[4] ={4, 3, 2, 1};
	int _nTemp = 0;
	int *_p = (int *)_arr;
	for (int i = 4; i >= 0; i--)
	{
		_nTemp <<= 8;
		_nTemp |= _arr[i];
		
	}
	
	printf("_nTemp = %#x\r\n", _nTemp);
	printf("*_p = %#x\r\n", *_p);
	return 0;
}

// 结果如下:
        _nTemp = 0x1020304
        *_p = 0x1020304

看到这,是否有发现我们判断CPU字节序同样可以使用指针进行判断?

/**
 * @brief:检测CPU的字节序
 * @retval:小端序返回TRUE,
 *			大端序返回FLASE
 */
bool check_cpu_byte_order(void)
{
    int n = 1;
    char *_pc = &n;
			
    return (1 == *_pc);
}

2.2、指针类型

这是指针里面的重中之重,掌握指针类型也是指针进阶学习的必需前提。

相信大家在做C语言练习题的时候,都有见过这样的一道题目,让我们分析变量a类型。对指针不熟的道友,一定要将下面的注释看完并看懂

int a;              // "a"与"int"结合,得到"int a"为整型变量
int *a;             // "a"先和"*"结合,得到"*a"为指针变量,再与"int"结合,得到"int *a"为整型指针变量
int a[10];          // "a"和"[10]"结合,得到"a[10]"为数组变量,再与"int"结合,得到"int a[10]"为整型数组,且有10个成员
int *a[10];         // 由于"[]"的结合优先级高于"*","a"先与"[10]"结合,得到"a[10]"为数组变量,然后再与"int *"结合,得到"int *a[10]"为指针数组,数组有10个成员且每个成员都是整型指针
int (*a)[10];       // "a"先与"*"结合,得到代表"*a"类型为指针,再与"int [10]"结合,得到"int (*a)[10]"为指针数组指针,且指向的数组有10成员
int **a;            // 二级指针"a"
int a(int);         // "a"与"(int)"结合,得到"a(int)"为传递参数为整型的函数,在与"int"结合得到"int a(int)"传递参数与返回值都是整型的函数
int *a(int);        // "a"与"(int)"结合,得到"a(int)"为传递参数为整型的函数,再与"int *"结合得到"int *a(int)"传递参数为整型返回值为整型指针的函数
int (*a)(int);      // "a"先与"*"结合,得到"*a"为指针,再与"(int)"结合得到"(*a)(int)"为指向传递参数为整型的函数指针,最后与"int"结合,得到"int (*a)(int)"为指向传递参数和返回值都是整型的函数指针
int *(*a(int))[10]; // "a"先与"(int)"结合得到"a(int)"传递参数为整型的函数,再与"*"结合得到"*a(int)"为传递参数为整型返回值为指针的函数,再与"[10]"结合得到"(*a(int))[10]"为函数数组,最后再与"int *"结合得到"int *(*a(int))[10]"为指向函数数组的指针,且函数的传递参数为整型,返回值为整型指针

这里指针的类型,我们只需要要变量名去掉,剩下的就是指针类型,我们从上面抽取指针进行验证:

int *;             // 整型指针
int (*)[10];       // 整型数组指针,变量先与指针结合,本质是指针
int **;            // 整型二级指针
int (*)(int);      // 传递参数和返回值都是整型的函数指针
int *(*(int))[10]; // 传递
int *(*(int))[10]; // 传递参数为整型,返回值为整型指针的数组函数指针

指针所指的类型:
       这里我们只需要将*和变量名去掉,剩下的就是指针指向的类型

int ;             // 整型
int ()[10];       // 整型数组
int *;            // 整型一级指针
int ()(int);      // 传递参数和返回值都是整型的函数
int (*(int))[10]; // 传递参数为整型,返回值为整型指针的数组

2.3、指针运算

指针的值与指针所指向的数据:

指针的值,指的是指针变量保存的数据(指向的变量的地址)。而指针所指向的值,是指针保存的内存地址上,内存保存的数据。

指针运算:

指针运算主要分为两种,其余的多为无意义项。这两种分别为:指针 \pm 整数,指针 - 指针

指针 \pm 整数:指针加减一个整数时,其具体意义与通常的数值运算不同,它是以指针对应数据类型为最小单元进行加减的,例如int型指针加1,实际是是偏移一个int数据类型,而不是一个字节。

指针 - 指针:需要时同类型的指针,且通常是在数组或者字符串中使用,其代表两个指针间隔多少个元素。

指针 “+” “-” 整数指针 - 指针其余运算都无意义说说其他运算为何无意义指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。指针运算,有意义的运算只有“+” 和 “-”,并且这些运算只有在数组或者字符串这类连续内存中才有意义。指针的“+”和“-”运算和通常的数值加减运算意义不同,因为指针运算是以成员变量为最小单位进行运算的。

#include <stdio.h>

int  main(int argc, const char argv[])
{
	int *_pI = (int *)0x2000;

	printf("address = %#x \r\n", (char *)_pI - 2);     // 偏移两个char变量
	printf("address = %#x \r\n", (short *)_pI - 2);    // 偏移两个short变量
	printf("address = %#x \r\n", (int *)_pI - 2);      // 偏移两个int变量    
	printf("address = %#x \r\n", (long *)_pI - 2);     // 偏移两个long变量
	printf("address = %#x \r\n", (float *)_pI - 2);    // 偏移两个float变量
	printf("address = %#x \r\n", (double *)_pI - 2);   // 偏移两个double变量
	
	return 0;
}

// 结果为:
    address = 0x1ffe 
    address = 0x1ffc 
    address = 0x1ff8 
    address = 0x1ff8 
    address = 0x1ff8 
    address = 0x1ff0 

2.4、数组和指针的关系

数组是内存中一段连续的且存储相同数据类型的内存块。并且数组名可以看做是一个指针。这样的特性,也导致了指针与数组的关系十分密切,在实际应用当中,许多数组的操作都可以用指针来代替。

#include <stdio.h>

int  main(int argc, const char argv[])
{
	int _nTmpArray[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	int _nTmpVal = 0;
	int *_pnTmp = _nTmpArray;

	for (int i = 0; i < 10; i++)
	{
		printf("Address of _nTmpArray[i] = %#x \r\n", &_nTmpArray[i]);
	}
	
	printf("_nTmpArray[4] = %d, *(_pnTmp + 4) = %d, *(_nTmpArray + 4) = %d\r\n",\
							_nTmpArray[4], *(_pnTmp + 4), *(_nTmpArray + 4));
	
	return 0;
}

// 结果为:
    Address of _nTmpArray[i] = 0xbfe959dc 
    Address of _nTmpArray[i] = 0xbfe959e0 
    Address of _nTmpArray[i] = 0xbfe959e4 
    Address of _nTmpArray[i] = 0xbfe959e8 
    Address of _nTmpArray[i] = 0xbfe959ec 
    Address of _nTmpArray[i] = 0xbfe959f0 
    Address of _nTmpArray[i] = 0xbfe959f4 
    Address of _nTmpArray[i] = 0xbfe959f8 
    Address of _nTmpArray[i] = 0xbfe959fc 
    Address of _nTmpArray[i] = 0xbfe95a00 
    _nTmpArray[4] = 4, *(_pnTmp + 4) = 4, *(_nTmpArray + 4) = 4

由结果我们可以分析出,数组名确实是可以看做一个指针,并且对数组名操作等同于对指针进行操作。 这里还有个有趣的点,你们猜猜 4[_nTmpArray] 结果是什么?
       没错 4[_nTmpArray] = _nTmpArray[4],原因在于_TmpArray[4] = *(_nTmpArray + 4);
       "[]"的作用等同于"*( )"的作用,所以" 4[_nTmpArray] = _nTmpArray[4]; "

指针数组与数组指针:
       指针数组,本质是一个数组,一个数组里面存放着多个同类型的指针。表示的是一个由指针变量组成的数组,也就是说其中的元素都是指针变量。

#include <stdio.h>

int  main(int argc, const char argv[])
{
	int _nTmpArray[3] = {1, 2, 3};
	int *_pnPtr[3];                   // “[]”的结合优先级高于“*”
	for (int i = 0; i < 3; i++)
	{
		_pnPtr[i] = &_nTmpArray[i];   // 赋值需要赋地址
	}
	for (int i = 0; i < 3; i++)
	{
		printf("Value of _nTmpArray[%d] = %d \r\n", i, *_pnPtr[i]);
	}
	
	return 0;
}

// 结果为:
    Value of _nTmpArray[0] = 1 
    Value of _nTmpArray[1] = 2 
    Value of _nTmpArray[2] = 3 
#include <stdio.h>

int  main(int argc, const char argv[])
{
	char *_pcArray[3] = {                    // 定义了一个字符指针数组,数组里面的成员字符串地址(字符指针)
							"first",
							"second",
							"third"
						};

	char **_p = _pcArray;                    // 需要指向指针,所以定义二级指针
	char *_pcPtr = NULL;  
	
	for (int i = 0; i < 3; i++)
	{
		_pcPtr = _pcArray[i];
		printf("_pcPtr[%d] = ", i);
		for (int j = 0; j < strlen(_pcPtr); j++)
		{
			printf("%c", _pcPtr[j]);
		}
		printf("\r\n");
	}

	for (int i = 0; i < 3; i++)
	{
		printf("_p[%d] = ", i);
		for (int j = 0; j < strlen(_p[i]); j++)
		{
			printf("%c", _p[i][j]);
		} 
		printf("\r\n");
	}
	return 0;
}

// 结果为:
    _pcPtr[0] = first
    _pcPtr[1] = second
    _pcPtr[2] = third
    _p[0] = first
    _p[1] = second
    _p[2] = third

数组指针:
        数组指针亦称之为“行指针”,本质上是一个指针,只是指针所指向的对象是数组。

#include <stdio.h>

int  main(int argc, const char argv[])
{
	int _nTmpArray[3][3] = {
							{1, 2, 3},
							{2, 3, 4},
							{4, 5, 6}
						   };

	int (*_pPtr)[3] = _nTmpArray;  			// 定义了一个整型数组指针,且该指针指向拥有3个成员
	for (int i = 0; i < 3; i++)
	{

		for (int j = 0; j < 3; j++)
		{
			printf("%d  ", _pPtr[i][j]);  	// 打印行的第j列元素
		}
		printf("\r\n");
	}
	return 0;
}

// 结果为:
    1  2  3  
    2  3  4  
    4  5  6 

二维数组行指针进行指针运算时,每个最小单元是按行来算的。

    printf("address of _pPtr = %#x, address of _pPtr++ = %#x \r\n", _pPtr, _pPtr++);

 这里可以看到,执行了"_pPtr++"指令,地址偏移量0X0C 也就是 3*sizeof(int)
      

 2.5、指针和结构体的关系

我们需要指针指向结构体的时候,需要定义一个结构体指针,并且将结构体的地址赋值给结构体指针,需要注意的就是对其成员的引用。

当我们通过结构体引用其成员时,使用的是“.”,而通过指针引用成员时需要使用“->”如下面代码所示

#include <stdio.h>

int  main(int argc, const char argv[])
{
    typedef struct 
    {
        char c;
        short s;
        int i;
    }tTempData_t;
	  
    tTempData_t tTempData = {1, 2, 3};   
    tTempData_t *ptTempData = &tTempData;
	  
    printf("ptTempData->c = %d, ptTempData->s = %d, ptTempData->i = %d\r\n", \
								ptTempData->c, ptTempData->s, ptTempData->i);
    printf("(*ptTempData).c = %d, (*ptTempData).s = %d, (*ptTempData).i = %d\r\n",\
								(*ptTempData).c, (*ptTempData).s, (*ptTempData).i);
	
	return 0;
}

// 结果为:
    ptTempData->c = 1, ptTempData->s = 2, ptTempData->i = 3
    (*ptTempData).c = 1, (*ptTempData).s = 2, (*ptTempData).i = 3

"."只是针对结构体变量对成员进行访问,而"->"是针对结构体指针变量先取值再访问成员,相对的多了一步取值的操作。 (*ptTempData).c 与 ptTempData->c 两种访问方式推荐使用后者。

2.6、指针与函数的关系

指针函数:返回值是一个指针的函数。
       声明指针函数的格式如下:

返回值类型 * 函数名([形参列表]);

指针函数:指向函数的指针,通过函数指针调用函数时,只需函数指针指向函数的入口地址(函数名)即可。函数指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。 
       声明函数指针的格式如下:

返回值类型 (*指针名)([形参列表]);

函数指针调用函数

#include <stdio.h>
/**
* @brief:比较两个数,返回大的
* @param:a 第一个数
* @param:b 第二个数
* @retval:返回两个数中大的那个数
*/
int compare_value(int _a, int _b)
{
	return ((_a > _b) ? (_a) : (_b));
}

int main(int argc, const char *argv[])
{
	int (*_pFunc)(int, int) = compare_value;

	printf("larger value is: %d \r\n", (*_pFunc)(10, 20));

	return 0;
}

// 结果为:
    larger value is: 20

函数指针作为参数传入函数中

#include <stdio.h>

/**
* @brief:比较两个数,返回大的
* @param:a 第一个数
* @param:b 第二个数
* @retval:返回两个数中大的那个数
*/
int compare_value(int _a, int _b)
{
	return ((_a > _b) ? (_a) : (_b));
}

/**
* @brief:比较三个数,返回最大的那个数
* @param:a 第一个数
* @param:b 第二个数
* @param:c 第三个数
* @param:(_pFunc)(int, int) 函数指针,传递比较函数
* @retval:返回三个数中最大的那个数
*/
int get_max_value(int _a, int _b, int _c, int (*_pFunc)(int, int))
{
	int _maxValue = 0;
	_maxValue = (*_pFunc)(_a, _b);
	_maxValue = (*_pFunc)(_c, _maxValue);

	return _maxValue;
}

int main(int argc, const char *argv[])
{
	int (*_pFunc)(int, int) = compare_value;
	
	printf("larger value is: %d \r\n", get_max_value(10, 20, 30, (*_pFunc)));		// (*_pFunc)同样可以改为compare_value,直接传入函数名

	return 0;
}

// 结果为:
    larger value is: 30

2.7、void 指针

void即"无类型","void *"即"无类型指针" ,可以指向任何数据类型甚至函数。所以我们可以用任意数据类型的指针对void指针赋值

不同于数据类型中void代表的为空,而(void *)代表指针无数据类型,其可以指向任何类型数据,而我们需要使用(void *)提取数据的时候,只需要将(void *)强制转换为对应所需的数据类型。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int _nTmp = 10;
	int *_pnTmp = &_nTmp;
	char *_pcTmp = "char pointer";
	void *_pvTmp = NULL;

	_pvTmp = _pnTmp;
	//printf("*_pnTmp = %d, *_pvTmp = %d\r\n", *_pnTmp, *_pvTmp);		// 这里会报错, error: invalid use of void expression,编译器不明白_pvTmp指向的数据类型

	printf("*_pnTmp = %d, *_pvTmp = %d\r\n", *_pnTmp, *(int *)_pvTmp);	// 需要将指针类型进行强制转换	
	_pvTmp = _pcTmp;
	printf("_pcTmp = %s, *_pvTmp = %s \r\n", _pcTmp, _pvTmp);			// 这里写法类似,为何能正确显示而不报错,因为这里需要显示的是字符串,编译器只需要一个地址即可,不需要知道数据类型
	return 0;
}

结果如下:
		*_pnTmp = 10, *_pvTmp = 10
		_pcTmp = char pointer, *_pvTmp = char pointer 

2.8、指向常量的指针和常量指针

指向常量的指针:指针指向的内容是常量,其不可以被修改。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	const int temp1 = 10;
	const int temp2 = 15;
	const int *_p = &temp1;					// 亦可以写为 int const *_p = &temp1;
	
	_p = &temp2;							// 该行代码可以正常编译并执行
	printf("*_p = %d\r\n", *_p);
	
	*_p = 15;								// 该行代码编译的时候直接报错
	printf("*_p = %d\r\n", *_p);

	return 0;
}

因为其指向内容为常量,所以不允许修改其指向内容,但是指针可以被修改。

常量指针:指针是常量,但是其指向内容不一定是常量

#include <stdio.h>
            
int main(int argc, const char *argv[])
{
    int temp1 = 10;
    int temp2 = 15;
    int *const _p = &temp1;
				
    _p = &temp2;						// 该句代码编译的时候直接报错
    printf("*_p = %d\r\n", *_p);
				
    *_p = 15;
    printf("*_p = %d\r\n", *_p);		// 该行代码可以正常编译并执行

    return 0;
}

指向常量的常量指针:指针与其指向的内容都为常量,皆不可修改

#include <stdio.h>
            
int main(int argc, const char *argv[])
{

    const int temp1 = 10;
    const int temp2 = 15;
    const int *const _p = &temp1;
				
    _p = &temp2;						// 该句代码编译的时候直接报错		
    printf("*_p = %d\r\n", *_p);			
				
    *_p = 15;							// 该句代码编译的时候直接报错
    printf("*_p = %d\r\n", *_p);

    return 0;
}

该类型指针与其指向内容都只有只读属性不允许修改
        
        如何区分指向常量的指针与指针常量?
        这里我们需要注意const的位置,
            若表达式中,*之前有const,则const是修饰指针指向的内容,属于指向常量的指针
            若表达式中,const在*之后,变量名之前,属于常量指针,
            若表达式中,*之前之后都有const,属于指向常量的常量指针。

2.9、指针安全问题

C语言中,指针的使用非常灵活且高效,但是我们需要注意对指针操作时候的安全问题。
      1)未初始化的指针
       未初始化的指针,通常被称之为“野指针”,就是我们不清楚其指向内存的具体位置,它有可能指向一个非法地址,若是我们轻易对其所指向内容,有可能会造成不可预知的错误,甚至引起程序崩溃的问题。常见问题有,代码紊乱不按正常顺序执行代码,内存变量被莫名修改,因为野指针不确定指向那,所以系统内存内容中任何一个内存单元都有可能被修改  

    int *_pn;
    *_pn = 1;

结果如下:
        Segmentation fault (core dumped)
    段错误,代码被强制中断,不能继续执行


      2)NULL指针
       NULL指针是一个特殊的指针变量,表示不指向任何东西,但是我们不可以对其进行取值操作,因为其不指向任何东西。

        int *_pn = NULL;

        printf("address of _pn = %#x \r\n", _pn);
        printf("value of _pn = %#x \r\n", *_pn);

结果如下:    
        address of _pn = 0 
        Segmentation fault (core dumped)

NULL指针的地址为nil,即为空值,也能理解为0
       
      3)指针越界
       指针越界,指的是,指针越出我们预期的规定范围,若指针越界之后我们对其所指向内容修改,有可能会造成不可预知的错误,甚至引起程序崩溃的问题,这里与野指针类似。   

#include <stdio.h>

int main(int argc, const char argv[])
{
	int _nTempArray[5] = {1, 2, 3, 4, 5};
	
	for (int i = 0; i < 7; i++)
	{
		printf("_nTempArray[%d] = %d \r\n", i, _nTempArray[i]);
	}
	
	return 0;
}

// 结果为:
    _nTempArray[0] = 1 
    _nTempArray[1] = 2 
    _nTempArray[2] = 3 
    _nTempArray[3] = 4 
    _nTempArray[4] = 5 
    _nTempArray[5] = 5 
    _nTempArray[6] = 134513760 
        

这里我们可以发现_nTempArray[5]和_nTempArray[6]的值是不可预知的,但是我们能访问,假如我们在其他地方越界访问了,将得到错误的结果。

 

仓促成文,不当之处,尚祈方家和读者批评指正。联系邮箱1772348223@qq.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值