C语言程序设计学习笔记:P9-指针_以下哪个打印指针地址的写法是正确的-程序员宅基地

技术标签: c语言  编程语言  数组  C语言程序设计  指针  软件开发  

本系列文章为浙江大学翁恺C语言程序设计学习笔记,前面的系列文章链接如下
C语言程序设计学习笔记:P1-程序设计与C语言
C语言程序设计学习笔记:P2-计算
C语言程序设计学习笔记:P3-判断
C语言程序设计学习笔记:P4-循环
C语言程序设计学习笔记:P5-循环控制
C语言程序设计学习笔记:P6-数据类型
C语言程序设计学习笔记:P7-函数
C语言程序设计学习笔记:P8-数组


一、指针

1.1 取地址运算

运算符&就是取地址运算符,我们在第一篇博客就看到了它:scanf(“%d”, &i);。里的&的作用就是获得变量的地址,它的操作数必须是变量。为什么变量有地址?因为C语言的变量是放在内存里的,比如一个int类型的变量在内存里面要占据4个字节。 那这个地址是个什么样的值呢?我们知道地址这些东西用16进制表达比较方便,我们来看看变量i的地址。

#include <stdio.h>

int main(void)
{
    
	int i = 0;
	printf("0x%x\n", &i);
	return 0;
}

运行,可以看出32位和64位编译器结果如下:
在这里插入图片描述
在这里插入图片描述


我们可以看出这些变量的地址很像个整数,我们来看看将地址赋值给一个整数看看会有什么后果。

#include <stdio.h>

int main(void)
{
    
	int i = 0;
	int p = &i;
	printf("0x%x\n", &i);
	printf("%p\n", &i);
	return 0;
}

可以看出有warning,提示我们int与int*类型不同。
在这里插入图片描述


如果现在我将地址强制转换成int,然后将这个int变量以16进制打印出来,看看会有什么结果。

#include <stdio.h>

int main(void)
{
    
	int i = 0;
	int p;
	p = (int)&i;
	printf("0x%X\n", p);
	printf("%p\n", &i);
	return 0;
}

32位编译器时,输出一样。
在这里插入图片描述
64位编译器时,作为int和作为地址输出就不一样了。
在这里插入图片描述


为了探索为什么不相等,我们写代码看下int和地址的大小分别是多少:

#include <stdio.h>

int main(void)
{
    
	int i = 0;
	int p;
	p = (int)&i;
	printf("0x%X\n", p);
	printf("%p\n", &i);
	printf("%lu\n",sizeof(int));
	printf("%lu\n", sizeof(&i));
	return 0;
}

可以看出64位编译器下,int大小是4个字节,这个地址取出来的大小是8个字节。
在这里插入图片描述
可以看出32位编译器下,int大小是4个字节,这个地址取出来的大小也是4个字节。
在这里插入图片描述
因此,地址的大小是否与int相同取决于编译器。我们使用printf打印地址时,应该使用%p。地址和整数的大小并不永远相等的,这和你的架构有关。


&必须接变量,不能对没有地址的东西取地址,比如:

• &(a+b)
• &(a++)
• &(++a)

我们测试一下:

#include <stdio.h>

int main(void)
{
    
	int i = 0;
	printf("%p",&(i++));

	return 0;
}

运行,结果如下:
在这里插入图片描述


我们试下&可不可以取以下东西的地址。

• 变量的地址
• 相邻的变量的地址
• &的结果的sizeof
• 数组的地址
• 数组单元的地址
• 相邻的数组单元的地址

我们来测试相邻变量的地址:可以看出两个变量放在一起,位置相差sizeof(int)=4个字节。
在这里插入图片描述
这是因为变量都放在堆栈里面,而根据C语言内存模型可以知道,在堆栈里面分配内存是自顶向下。所以我们先写的变量i的地址更高,后写的变量p地址更低,但它们是紧挨着的。它们的差距就是4,这个4就是sizeof(int)。
在这里插入图片描述


我们再来看看对数组取地址:

#include <stdio.h>

int main(void)
{
    
	int a[10];

	printf("%p\n", &a);
	printf("%p\n", a);
	printf("%p\n", &a[0]);
	printf("%p\n", &a[1]);

	return 0;
}

可以看出 &a = a = &a[0]
在这里插入图片描述

1.2 指针

如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? 答案是肯定的,比如我们用过的scanf函数,scanf(“%d”, &i);。根据scanf()的原型,我们需要一个参数能保存别的变量的地址。如何表达能够保存地址的变量?这时候就需要指针。

普通变量的值是实际的值,指针就是保存地址的变量,指针变量的值是具有实际值的变量的地址,示意图如下:
在这里插入图片描述
指针的用法如下

int i;
int* p = &i;
int* p,q;  //定义了一个指针p和一个int变量q。我们不是把*加给了int,而是把*加给了p。
int *p,q;  //和上面那个一样的意思。
如果要定义两个指针,那就是int *p,*q

指针可以作为作为参数

void f(int *p);
在被调用的时候得到了某个变量的地址:
int i=0; f(&i);
在函数里面可以通过这个指针访问外面的这个i

我们通过一个例子来进行测试,看是否能在函数里面读取到外面变量的地址。

#include <stdio.h>

void f(int *p);

int main(void)
{
    
	int i = 6;
	printf("&i=%p\n", &i);
	f(&i);

	return 0;
}

void f(int *p)
{
    
	printf(" p=%p\n", p);
}

运行,可以看出在函数内部也能得到变量i的地址。
在这里插入图片描述


访问那个地址上的值
我通过指针读到变量的地址后,如果想改变那个变量的值,该怎么办呢?这时候需要使用 *,它是一个单目运算符,用来访问指针的值所表示的地址上的变量。

• 可以做右值也可以做左值
• int k = *p;
• *p = k+1;

我们来测试一下是否可以通过*访问到指针表示的地址上的变量。

#include <stdio.h>

void f(int *p);

int main(void)
{
    
	int i = 6;
	printf("&i=%p\n", &i);
	printf("i=%d\n", i);
	f(&i);

	return 0;
}

void f(int *p)
{
    
	printf(" p=%p\n", p);
	printf(" *p=%d\n", *p);
}

运行,可以发现成功访问到了。
在这里插入图片描述
那我们能不能改变那个变量的值呢?

#include <stdio.h>

void f(int *p);

int main(void)
{
    
	int i = 6;
	printf("&i=%p\n", &i);
	printf("i=%d\n", i);
	f(&i);
	printf("i=%d\n", i);

	return 0;
}

void f(int *p)
{
    
	printf(" p=%p\n", p);
	printf(" *p=%d\n", *p);
	*p = 26;
}

运行,可以发现i的值被改变了。
在这里插入图片描述

左值之所以叫左值

• 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果:
• a[0] = 2;
• *p = 3;
• 是特殊的值,所以叫做左值

指针的运算符&和*

• 他们互相反作用
• *&yptr -> * (&yptr) -> * (yptr的地址)-> 得到那个地址上的变量 -> yptr
• &*yptr -> &(*yptr) -> &(y) -> 得到y的地址,也就是yptr -> yptr

为什么int i; scanf(“%d”, i);编译没有报错?
因为i是个整数,且刚好在32位架构下,整数和地址一样大。你把整数传进去和把地址传进去,scanf没看出区别。


1.3 指针的使用

指针的应用场景1:交换两个变量的值

#include <stdio.h>

void swap(int *pa, int *pb);

int main(void)
{
    
	int a = 5;
	int b = 6;
	swap(&a, &b);
	printf("a=%d,b=%d\n", a,b);

	return 0;
}

void swap(int *pa, int *pb)
{
    
	int t = *pa;
	*pa = *pb;
	*pb = t;
}

运行,可以看出a和b的值成功交换了。
在这里插入图片描述


指针应用场景2:函数返回多个值,某些值就只能通过指针返回。比如传入的参数实际上是需要保存带回的结果的变量。

现在我有一个数组,需要返回最大值和最小值。我可以使用两个变量保存最大值与最小值,并将两个变量的地址作为参数传入函数。在函数中通过指针访问那两个变量并修改它们的值。

#include <stdio.h>

void minmax(int a[], int len, int *max, int *min);

int main(void)
{
    
	int a[] = {
    1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55};
	int min,max;
	minmax(a, sizeof(a)/sizeof(a[0]), &min, &max);
	printf("min=%d,max=%d\n", min, max);

	return 0;
}

void minmax(int a[], int len, int *min, int *max)
{
    
	int i;
	*min = *max = a[0];
	for (i = 1; i < len; i++) {
    
		if (a[i] < *min) {
    
			*min = a[i];
		}
		if (a[i] > *max) {
    
			*max = a[i];
		}
	}
}

运行,可以发现成功找到了最大值与最小值。
在这里插入图片描述


指针应用场景3:函数返回运算的状态,结果通过指针返回

• 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
  • -1或0(在文件操作会看到大量的例子)
• 但是当任何数值都是有效的可能结果时,就得分开返回了
  • 状态用函数的return来返回,实际的值通过指针参数来返回
  • 后续的语言(C++,Java)采用了异常机制来解决这个问题

举例:两个整数做除法

#include <stdio.h>

int divide(int a, int b, int *result);

int main(void)
{
    
	int a=5;
	int b=2;
	int c;
	if (divide(a, b, &c)) {
    
		printf("%d/%d=%d\n",a,b,c);
	}

	return 0;
}

int divide(int a, int b, int *result)
{
    
	int ret = 1;
	if (b == 0) ret = 0;
	else {
    
		*result = a/b;
	}
	return ret;
}

可以看出,如果无法相除,就会返回0。如果成功,就会返回1,且结果通过指针保存在c中。
在这里插入图片描述


指针最常见的错误:定义了指针变量,还没有指向任何变量,就开始使用指针
我这里定义了一个指针,现在试着让它指向的值更改为12。

#include <stdio.h>

int main(void)
{
    
	int i = 6;
	int *p;
	int k = 12;
	*p = 12;

	return 0;
}

运行,可以看出报错。
在这里插入图片描述

我们来分析原因:我们知道,所有的本地变量都不会有默认的初始值,如果没有对它做过赋值,这个p里面没有明确的值,可能是个乱七八糟的东西。如果把它当作地址的话,可能会指向一片莫名其妙的地方。当你使用*p=12时,你试图向那个地方写入12,而这个地方可能是不能写的。
在这里插入图片描述
我们来测试下,如果让 *p有个初始值为0,这个地方是肯定不能写的,现在让它写入12看看。可以看出直接中断。
在这里插入图片描述

1.4 指针与数组

当我们向函数传一个普通变量,参数接收到的是个值。如果传一个指针,参数也是接收到的一个值,这个时候的值是地址。当我们把数组作为一个值传递给函数,函数的参数表内有一个变量去接受那个数组,这个变量到底接收到了什么?我们拿上面写的minmax这个函数来做实验。

为什么在minmax函数内不能用sizeof算出这个a[]的元素个数呢? 到底它的sizeof是多少呢?我们在main函数和minmax函数中分别取打印a[]的sizeof。同时打印a[]的地址。

	#include <stdio.h>

	void minmax(int a[], int len, int *max, int *min);

	int main(void)
	{
    
		int a[] = {
     1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55 };
		int min, max;
		printf("main sizeof(a)=%lu\n", sizeof(a));
		printf("main a=%p\n", a);
		minmax(a, sizeof(a) / sizeof(a[0]), &min, &max);
		printf("min=%d,max=%d\n", min, max);

		return 0;
	}

	void minmax(int a[], int len, int *min, int *max)
	{
    
		int i;
		printf("minmax sizeof(a)=%lu\n", sizeof(a));
		printf("minmax a=%p\n", a);
		*min = *max = a[0];
		for (i = 1; i < len; i++) {
    
			if (a[i] < *min) {
    
				*min = a[i];
			}
			if (a[i] > *max) {
    
				*max = a[i];
			}
		}
	}

运行,可以看出在32位编译器条件下,minmax函数中a[]的大小为4,刚好为一个指针的大小。同时,minmax函数与main函数中a[]的地址一模一样,说明这两个完全是同一个数组,相当于一个演员演了一对双胞胎。
在这里插入图片描述
为了来测试这两个数组就是同一个,我们在minmax函数中将a[0]的值更改为1000,然后去main函数看看a[0]的值。我们运行程序,可以看出a[0[=1000。
在这里插入图片描述
所以,函数参数表里面的数组就是指针!这也就解释了为什么在参数表内不要在a[]的括号内写数字,为什么在函数内无法用sizeof求出a[]的大小。


既然参数表里的数组就是个指针,那我们在参数表内把它写成个指针,行不行?答案是可以的。
在这里插入图片描述


可以看出,我们虽然传入了一个指针,但是对它做了一系列数组的操作。看起来数组和指针似乎存在某种联系,我们有以下总结:
1、传入函数的数组成了什么?

函数参数表中的数组实际上是指针
sizeof(a) == sizeof(int*)
但是可以用数组的运算符[]进行运算

2、数组参数

以下四种函数原型是等价的:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);

3、数组变量是特殊的指针

数组变量本⾝身表达地址,所以
  int a[10]; int*p=a; // 无需用&取地址
  但是数组的单元表达的是变量,需要用&取地址
  a == &a[0]
[]运算符可以对数组做,也可以对指针做:
  p[0] <==> a[0]
*运算符可以对指针做,也可以对数组做:
  *a = 25;
  *p = a[0] = 25
数组变量是const的指针,所以不能被赋值
  int b[] = a;  //不可以
  int *q = a;  //可以
  int a[] <==> int * const a  这个const加在这里告诉这个a是个常数不能被改变

1.4 指针与const

const是个修饰符,加在变量前面,告诉这个变量不能被修改。指针是一种变量,由两部分内容组成:一个是指针本身,一个是指针所指的那个变量。那么在这种情况下,当指针与const遇到了,指针本身可以是const,指针指向的那个值可以是const。他们有什么样的区别和联系呢?

1、指针是const

表示一旦得到了某个变量的地址,不能再指向其他变量
int * const q = &i;  // q这个指针是 const,即q的值(i的地址)不能被改变,q指向i这个事实不能改变了。
*q = 26;             // OK,通过q做一些访问并改变i这个变量的值是可以的
q++;                 // ERROR

2、所指是const

表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
const int *p = &i;
*p = 26; // ERROR! (*p) 是 const
i = 26; //OK
p = & j; //OK

3、这里有三种写法,看看什么意思

int i;
const int* p1 = &i; //不能通过*p1去修改i的值
int const* p2 = &i; //不能通过*p2去修改i的值
int *const p3 = &i; //p3必须指向i,不能指向其他地方。

判断哪个被const了的标志是const在*的前面还是后面。const在后面,指针不能修改。const在前面,不能通过指针修改值。


4、转换
我们总是可以把一个非const的值转换成const的。

void f(const int* x); //代表你给我一个指针,我在我的函数内部不会去动这个指针。
int a = 15;
f(&a);  // ok,函数需要const int的指针,我们给了1个非const的指针,没问题。
const int b = a;

f(&b); // ok
b = a + 1; // Error!

这种我们拿来做什么呢?当要传递的参数的类型比地址大的时候(传一些结构体之类的),这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改。


5、const数组
数组也是一种特殊的指针,因此也可以搭配const使用。

const int a[] = {
    1,2,3,4,5,6,};
数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
所以必须通过初始化进行赋值

6、保护数组值

• 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
• 为了保护数组不被函数破坏,可以设置参数为const
  • int sum(const int a[], int length);

小测验

1、对于:

int a[] = {5, 15, 34, 54, 14, 2, 52, 72};
int *p = &a[5];
则p[-2]的值是?
A. 编译出错,因为数组下标越界了
B. 运行出错,因为数组下标越界了
C. 54
D. 2

答案:C

2、如果:

int a[] = {0};
int *p = a;
则以下哪些表达式的结果为真?
A. p == a[0]
B. p == &a[0]
C. *p == a[0]
D. p[0] == a[0]

答案:B、C、D

3、以下变量定义:

int* p,q;
中,p和q都是指针。

正确答案:错误

4、对于:

int a[] = {5, 15, 34, 54, 14, 2, 52, 72};
int *p = &a[1];
则p[2]的值是?

答案:54

二、指针运算

2.1 指针运算

我们都知道1+1=2。但是,对于指针呢?让指针加1,结果是真正加1了吗?我们来测试一下。

#include <stdio.h>
int main(void)
{
    
	char ac[] = {
    1,2,3,4,5,6,7,8,9};
	char *p = ac;
	printf("p  =%p\n", p);
	printf("p+1=%p\n", p+1);
	return 0;
}

运行,可以看出相差1。
在这里插入图片描述
那我们把char类型的数组更改为int类型的呢?可以看出相差4。
在这里插入图片描述
sizeof(char)=1,sizeof(int)=4。因此,指针加1不是让地址值加1,而是在地址值上加1个sizeof(所指向值的类型)。示意图如下所示:
在这里插入图片描述


因此,对于1+1这个问题:

• 给一个指针加1表示要让指针指向下一个变量
int a[10];
int *p = a;
*(p+1) —> a[1]
*(p+n) —> a[n]
• 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针计算

• 这些算术运算可以对指针做:
  • 给指针加、减一个整数(+, +=, -, -=)
  • 递增递减(++/--)
  • 两个指针相减

我们来看看指针相减是怎么一回事。我们分别定义一个char类型的数组和int类型的数组,用两个指针变量保存第一个元素的地址和第6个元素的地址。接着让这两个指针相减,看结果是多少。

#include <stdio.h>

int main(void)
{
    
	char ac[] = {
    1,2,3,4,5,6,7,8,9};
	char *p = &ac[0];
	char *p1 = &ac[5];
	printf("p  =%p\n", p);
	printf("p1 =%p\n", p1);
	printf("p1-p=%d\n", p1-p);

	int ai[] = {
    1,2,3,4,5,6,7,8,9 };
	int *q = &ai[0];
	int *q1 = &ai[6];
	printf("q  =%p\n", q);
	printf("q1 =%p\n", q1);
	printf("q1-q=%d\n", q1 - q);

	return 0;
}

可以看出,两个数组相减不是得到两个地址的差值,而是还要去除以类型的大小,表示这段区域可以放多少个这样的值。
在这里插入图片描述


我们在程序里面经常看到*p++这个东西

• 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
• *的优先级虽然高,但是没有++高
• 常用于数组类的连续空间操作
• 在某些CPU上,这可以直接被翻译成一条汇编指令

我们写一些代码来看看*p++的用法。

#include <stdio.h>

int main(void)
{
    
	char ac[] = {
    1,2,3,4,5,6,7,8,9, -1};
	char *p = ac;
	int i;
	//原始的方法遍历数组
	for (i = 0; i < sizeof(ac) / sizeof(ac[0]); i++) {
    
		printf("%d ", ac[i]);
	}
	printf("\n");

	//使用*p++的方法遍历数组
	while (*p != -1) {
    
		printf("%d ", *p++);
	}
	printf("\n");

	return 0;
}

指针比较

<, <=, ==, >, >=, != 都可以对指针做
比较它们在内存中的地址
数组中的单元的地址肯定是线性递增的

0地址
现在的操作系统都是多进程的操作系统,它的基本管理单元叫做进程。什么是进程?比如你运行了一个浏览器,那就是个进程。我们打开了Visual Studio进行编程,Visual Studio也是个进程。操作系统会给每个进程一些虚拟的空间,所有的程序在运行时都以为自己有一片从0开始的连续的空间。因此,任何程序都有0地址,但是这个0地址不能碰,有的甚至都不能读。

• 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
• 所以你的指针不应该具有0值
• 因此可以用0地址来表示特殊的事情:
  • 返回的指针是无效的
  • 指针没有被真正初始化(先初始化为0)
• NULL是一个预定义的符号,表示0地址
  • 有的编译器不愿意你用0来表示0地址

指针的类型
指针是有类型的,不同类型的指针不能互相赋值。我们来举个例子,有个char类型的指针和int类型的指针,现在将char类型指针赋值给int类型的指针,看会有什么后果。

#include <stdio.h>
int main(void)
{
    
	char ac[] = {
    1,2,3,4,5,6,7,8,9,-1};
	char *p = ac;
	int  ai[] = {
    1,2,3,4,5,6,7,8,9,-1};
	int  *q = ai;
	q=p;
	return 0;
}

运行,可以看出不报错,但是有warning。
在这里插入图片描述
这样做会导致一些不好的后果。如果我将指针p赋值给q,现在我让*q=0,按理说应该把0赋值给ac[0]。但实际上会让ac[0]、ac[1]、ac[2]、ac[3]都赋值为0。
在这里插入图片描述

指针赋值总结如下

• 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
• 但是指向不同类型的指针是不能直接互相赋值的
• 这是为了避免用错指针

指针的类型转换
如果我就想做指针类型转换怎么办呢?实际上是可以做的,不过不要乱用,一般在malloc的时候搭配void*使用。

• void* 表示不知道指向什么东西的指针
  • 计算时与char*相同(但不相通)
• 指针也可以转换类型
  • int *p = &i; void*q = (void*)p;
• 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
  • 我不再当你是int啦,我认为你就是个void!

总结:指针的作用

用指针来做什么
• 需要传入较大的数据时用作参数
• 传入数组后对数组做操作
• 函数返回不止一个结果
• 需要用函数来修改不止一个变量
• 动态申请的内存...

2.2 动态内存分配

之前我们讲过,如果输入数据时,先告诉你个数,然后再输入,要记录每个数据。C99可以用变量做数组定义的大小,C99之前呢?那就只能用动态内存分配,如下面一行所示:

int *a = (int*)malloc(n*sizeof(int));

我们来看看malloc的定义:

• 使用前需要包含stdlib.h这个头文件:  #include <stdlib.h>
• malloc函数原型为: void* malloc(size_t size);
• 向malloc申请的空间的大小是以字节为单位的
• 返回的结果是void*,需要类型转换为自己需要的类型
• (int*)malloc(n*sizeof(int))

我们写出代码来看看malloc如何工作的:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
	int number;
	int *a;
	int i;
	printf("输入数量:");
	scanf_s("%d", &number);

	//申请一片number*sizeof(int)个字节的内存,然后进行类型转换
	//现在我们可以将a当作int类型的数组使用了
	a=(int*)malloc(number*sizeof(int));
	for (i = 0; i < number; i++) {
    
		scanf_s("%d", &a[i]);
	}
	for (i = number - 1; i >= 0; i--) {
    
		printf("%d ", a[i]);
	}
	//这片内存是借的,用完需要还
	free(a);

	return 0;
}

运行,可以看到成功分配了内存,并倒序打印了数组中的值。
在这里插入图片描述


如果malloc申请时没空间了怎么办?如果申请失败则返回0,或者叫做NULL。你的系统能给你多大的空间?我们写出代码来看下。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
	void *p;
	int cnt = 0;
	while ((p = malloc(100 * 1024 * 1024))) {
    
		cnt++;
	}
	printf("分配了%d00MB的空间\n", cnt);

	return 0;
}

在32位编译平台下,分配了1900MB。
在这里插入图片描述
在64位编译平台下,分配内存45G。
在这里插入图片描述


free()

• 把申请得来的空间还给“系统”
• 申请过的空间,最终都应该要还
  • 混出来的,迟早都是要还的
• 只能还申请来的空间的首地址
• free(0)?

我们申请一段内存,然后将首地址++,并释放该地址,看会发生什么。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
	char *p;
	int cnt = 0;
	p = malloc(100 * 1024 * 1024);
	p++; 
	free(p);

	return 0;
}

可以看出,直接抛出异常。
在这里插入图片描述
我们再来看看释放一个不是申请来的内存看看有什么结果。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    
	int *p;
	int i;
	p = &i;
	free(p);

	return 0;
}

可以看出也抛出异常。
在这里插入图片描述

如果我们free(NULL)不会出错。
在这里插入图片描述
这是因为NULL就是0地址,0地址不可能是个有效的地址,它不可能是malloc来的。free也是一个函数,如果给它一个NULL,那它就不做事情。只是,有什么必要做这件事情呢?这是因为良好习惯就是:有一个指针出来了,我们先初始化为0。如果由于某些原因我们没有去malloc分配一片内存给它或者malloc得到一个失败的结果,我们去free那个指针没问题。


常见问题

• 申请了没free—>长时间运行内存逐渐下降
  • 新手:忘了
  • 老手:找不到合适的free的时机
  如果程序小,基本没影响,程序结束后内存被释放。如果程序很大,就会造成严重后果。
• free过了再free
• 地址变过了,直接去free

小测验

1、对于以下代码段,正确的说法是:

char *p;
while (1) {
    
    p = malloc(1);
    *p = 0;
}

A. 最终程序会因为没有没有空间了而退出
B. 最终程序会因为向0地址写入而退出
C. 程序会一直运行下去
D. 程序不能被编译

答案:B

2、对于以下代码段:

int a[] = {
    1,2,3,4,5,};
int *p = a;
int *q = &a[5];
printf("%d", q-p);sizeof(int)4时,以下说法正确的是:

A. 因为第三行的错误不能编译
B. 因为第三行的错误运行时崩溃
C. 输出5
D. 输出20

答案:C

3、使用malloc就可以做出运行时可以随时改变大小的数组
答案:错误

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/InnerPeaceHQ/article/details/121546043

智能推荐

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Access denied for user 'chong'@'localhost-程序员宅基地

文章浏览阅读5.6k次。那是因为这个账号没有权限,使用root就可以,或者登陆root去给chong这个账号添加权限_com.mysql.jdbc.exceptions.jdbc4.mysqlsyntaxerrorexception: access denied for

swust oj 249 凸包面积_第一个纵坐标最小的点记为遍历-程序员宅基地

文章浏览阅读750次。凸包面积 1000(ms) 65535(kb) 1078 / 3483Tags: 分治法麦兜是个淘气的孩子。一天,他在玩钢笔的时候把墨水洒在了白色的墙上。再过一会,麦兜妈就要回来了,麦兜为了不让妈妈知道这件事情,就想用一个白色的凸多边形把墙上的墨点盖住。你能告诉麦兜最小需要面积多大的凸多边形才能把这些墨点盖住吗? 现在,给出了这些墨点的坐标,请帮助麦兜计算出覆盖这些_第一个纵坐标最小的点记为遍历

结构体指针,C语言结构体指针详解-程序员宅基地

文章浏览阅读3.8k次,点赞10次,收藏36次。指向结构体变量的指针#include <stdio.h># include <string.h>struct AGE{int year;int month;int day;};struct STUDENT{char name[20]; //姓名int num; //学号struct AGE birthday; //生日float score..._结构体指针

SpringBoot项目启动时:Failed to initialize connector [Connector[HTTP/1.1-8080]]_springboot failed to initialize connector 8080-程序员宅基地

文章浏览阅读5.7k次。用MyEcplise2017启动SpringBoot项目时突然闪退,再启动运行项目时就报错Failed to initialize connector [Connector[HTTP/1.1-8080]]从网上找到解决方案是端口已经被使用打开任务管理器看到有两个javaw.exe,退掉MyEcplise时,还有一个,点击结束进程,重启MyEcplise重新运行项目就行。特写此文..._springboot failed to initialize connector 8080

[Andrioid开发] Splash界面/用户协议与隐私政策弹窗/界面开发_android 隐私协议弹窗-程序员宅基地

文章浏览阅读4k次,点赞3次,收藏21次。[Andrioid开发] Splash界面/用户协议与隐私政策弹窗/界面开发启动页界面开发、首次启动时的启动页用户协议与隐私政策弹窗,只要不点击同意每次打开都会显示弹窗,同意后立即跳转到主界面,当下次再进入软件就是两秒后自动跳转到主界面。_android 隐私协议弹窗

java实现数字千分位的显示_java 数字显示k w-程序员宅基地

文章浏览阅读1.3w次。由于项目中要求输入的数字用千分位显示,数字保留两位小数,而且要求再删除数字的时候也要求删除后的数字也要是千分位显示,好像表达的有点不清楚,贴代码吧,作为一个小工具吧。 /** * 格式化数字为千分位显示; * @param 要格式化的数字; * @return */ public static String fmtMicrometer(String text)_java 数字显示k w

随便推点

第三课: 图像的 BLOB 分析处理流程(clip_region_rel,edges_sub_pix,segment_contours_xld等)---Circles.hdev-程序员宅基地

文章浏览阅读4.4k次。所用到的算子:1、boundary(Region : RegionBorder : BoundaryType : ) *提取区域的边界,实质是像素 boundary (Region, RegionBorder, 'inner')2、clip_region_rel(Region : RegionClipped : Top, Bottom, Left, Right : )*通..._segment_contours_xld

最后一个单词的长度_输出最后一个单词的长度-程序员宅基地

文章浏览阅读123次。给定一个仅包含大小写字母和空格' '的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。如果不存在最后一个单词,请返回 0。说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串class Solution {public: int lengthOfLastWord(std::string s) { if (s.empty()) return 0; int..._输出最后一个单词的长度

css实现实现图片切换——使用a标签做锚点+overflow:hidden实现-程序员宅基地

文章浏览阅读479次。一.a标签设置锚点1、使用id定位:<a href="#fifth">锚点跳到5F</a><p style="height: 1300px"> 中间有很多内容</p><div id="fifth" style="height: 1300px"> 这里是5F的内容</div>..._a标签加入图标 切角

2018.11.6 PION 模拟赛-程序员宅基地

文章浏览阅读69次。期望:100 + 40 + 50 = 190实际:60 + 10 + 50 = 120考得好炸啊!!T1数组开小了炸掉40,T2用 int 读入 long long ,int存储 long long 炸掉 20T3可以吧for维护最大值变成o(1),但是木想到啊,只想写暴力了。。。w(゚Д゚)w最近考试低级错误一个接一个啊!!noip肿么玩啊。。简直没法好好玩耍了。感..._期望的线性性质,考虑每个数的贡献,一个数能够做出1的贡献当且仅当它被自己删掉,所

工厂模式(初学)-程序员宅基地

文章浏览阅读259次。是一种创建型设计模式,旨在通过一个工厂类(简单工厂)来封装对象的实例化过程

[MFC] CWnd类总结-程序员宅基地

文章浏览阅读7.8k次,点赞10次,收藏73次。一、MFC 类别阶层架构二、CWnd类CWnd是MFC的一个窗口类,这个类里几乎封装了所有关于窗口操作的API函数。在Windows系统里,一个窗口的属性分两个地方存放:一部分放在“窗口类”里头,如上所述的在注册窗口时指定;另一部分放在Windows Object本身,如:窗口的尺寸,窗口的位置(X,Y轴),窗口的Z轴顺序,窗口的状态(ACTIVE,MINIMIZED,MAXM..._cwnd

推荐文章

热门文章

相关标签