跳到主要内容

指针与函数

指针部分

64位环境 指针类型占用8个字节

32位环境 指针类型占用4个字节

变量与地址

变量对某块内存的抽象表示

指针 -> 地址

变量名 -> 抽象出来的某块空间的别名

指针与指针变量

int i = 1;
int *p = &i; //用于存放整型的指针
int ** q = &p; //*q指的是取q里面的地址值
备注

TYPE NAME = VALUE 要区分int *p = &i;是定义int * 类型的p,再把p取i的地址

以下是各变量的值:

i = 1;
&i = 0x2000;
p = 0x2000;
&p = 0x3000;
*p = 1;

q = 0x3000;
&q = 0x4000;
*q = 0x2000; //*q即是p的地址,不用刻意考虑什么多级指针
**q = 1;
提示

指针的大小不取决于数据类型,但是在定义的时候,需要定义数据类型来帮助其在运算的时候读取值的时候获取多少位来代表值。

定义与初始化

空指针与野指针

int *p = NULL; //空指针
int *p = 0x40303; //野指针
提示

空指针(NULL为宏,在预编译的时候补充为((void *)0))

危险

如果只定义了int *p并对其取值,可能会出现段错误,如果你对没有权限赋值的进行赋值的结果,肯定出现段错误。所以如果不确定指针的内容,请把他赋值为NULL

void类型指针

百搭类型,所有类型的值可以赋给他,也可以把他赋给其他类型。

char *s = "hello";
void *i = s; //对字符串指针的赋值

指针运算

& * 关系运算 ++ --

指针与数组

指针与一维数组

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

int main () {
int a[3] = {1, 2, 3};
// a是常量 p是变量
int *p = a;

for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
printf("%p -> %d\n", p+i, *(p+i));
//这里的p+i是指针运算,代表往下数i个的p的数据类型长度
}
}

p+1为什么不是单纯的p变量加一请参考章节指针与指针变量的笔记。同时需要注意,在以上描述中数组a[i]a是常量,p是变量

用以下方式可以表示数组:

a[i]: a[i]=*(a+i)=*(p+i)=p[i]

危险

对于p++ -> p = p + 1 p+1 -> p+1 。由两关系式,可以看出p++ != p+1

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

int main () {
int a[3];
int *p = a;

for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
scanf("%d", p++);
}

for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
printf("%d\n", *(p++)); //这里的p变量没有回退到赋值开始地址
}
}
#include <stdio.h>
#include <stdlib.h> //exit(0);
#include <string.h>
#define STRSIZE 1024

int main() {
int a[3];
int *p=a;
int i;
for(i=0;i<sizeof(a)/sizeof(*a);i++){
scanf("%d",p++);
}
for(i=0;i<sizeof(a)/sizeof(*a);i++){
printf("%p->%d\n",&a[i],a[i]);
}
p=a;
for(i=0;i<sizeof(a)/sizeof(*a);i++,p++){
printf("%p->%d\n",p,*p);
}
}
/* 以下是错误输出
1
2
3
0x7ffcc71063a8->1
0x7ffcc71063ac->2
0x7ffcc71063b0->3

0x7ffcc71063ac->1
0x7ffcc71063b0->2
0x7ffcc71063b4->3
*/
提示

以上代码中p++是放在for语句下,原因是如果printf("%p->%d\n",p,*p++);写这样的话printf函数是自右向左进行参数压栈计算,会把p自加后赋值给p,导致第一个参数不是我们想要的,需要注意。

数组还可以这样定义:

int *p = (int [3]){1,2,3} //匿名数组

指针与二维数组

二维数组本质上是一维数组的扩展,也是在内存上连续存储,所以不用通过二级指针来定义他,可以通过行列转换进行表示。

提示

a[i][j] = *(*(p+i)+j) 也就是说,数组a[i]存储了每一行地址,行指针变为列指针(*(p+i)+j)确定数值的地址。

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

int main() {
int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
int (*p)[3] = a;

for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
for (int j = 0;j < sizeof(*a)/sizeof(**a);j++) {
printf("%d ", *(*(p+i)+j));
}
}
}
#include <stdlib.h>
#include <stdio.h>

int main() {
int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
int *p = &a[0][0];

for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
for (int j = 0;j < sizeof(*a)/sizeof(**a);j++) {
printf("%d ",*(p+(i * sizeof(*a)/sizeof(**a))+j));
}
}
}

数组指针

一个指针指向一个数组,也就是二维数组的另外一种实现

[存储类型] 数据类型 (*指针名)[下标] = 值

int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
int *p = *a
int (*p)[3] = a;
#include <stdlib.h>
#include <stdio.h>

int main() {
int a[2][3] = {{1, 2, 3},{ 4, 5, 6}};
int (*p)[3] = a;

for (int i = 0;i < sizeof(a)/sizeof(*a);i++) {
for (int j = 0;j < sizeof(*a)/sizeof(**a);j++) {
printf("%d ", *(*(p+i)+j));
}
}
}

指针数组

放指针的数组

[存储类型] 数据类型 *数组名[下标] = 值

指针数组排序代码:

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

int main() {
char *name[5] = {"golang", "java", "c", "dart", "erlang"};
int k;
char *tmp;
for (int i = 0;i < (sizeof(name)/sizeof(*name))-1;i++) {
k = i;
for (int j = i+1;j < (sizeof(name)/sizeof(*name));j++) {
if (strcmp(name[k], name[j]) > 0) {
k = j;
}
}
if (k != i) {
tmp = name[i];
name[i] = name[k];
name[k] = tmp;
}
}
for (int i = 0;i < (sizeof(name)/sizeof(*name));i++) {
printf("%s\n", *(name+i));
}
}

字符数组指针

字符指针和字符数组的区别

字符数组指针见以下代码示例:

char str[]="Hello world!"//*str
char *p = str;
puts(p);

str = "name" //这个是不允许的,str是常量
strcpy(str,"world");//赋值请使用内置string.h的函数
char *str = "hello";
printf("%d %d\n",sizeof(str),strlen(str));
strcpy(str,"world");
puts(str);

以上代码会出现以下报错:

[root@node1 ~]# ./main
8 5
段错误 (核心已转储)
备注

出现以上报错的原因 原因是str指向的是"hello"常量,字符常量,常量无法改变。使用str="world"代替即可。当然如果是字符数组str[]则应该使用strcpy(str,"world")函数。

const与指针

const 是把某些变量转变为常量

const float pi = 3.14; // 常量化变量
//如何修改?但是这是没有意义的操作
float *p = &pi;
*p = 3.14159

指针常量与常量指针的区别

如何识别?先看到指针就是指针 先看到常量就是常量

  • 常量指针-指向的内存内容不能通过这个指针修改
const int *p;
int const *p;
  • 指针常量-指向的地址不能变但是可以通过这个指针修改内存的值
int *const p;
const int *const p;
提示

不管是指针常量还是常量指针,他的限制是不能通过你定义的变量来改变你把变量变成常量的事实,但是你还是可以通过其他方式把里面的内容修改过来。 同时常量还可以保证你的内容不被修改,应用在工程上,主要体现在数据的保护。

多级指针

多级指针有点像链表意义只是把内容存放了下一层地址,没有实际内容,实际的有具体的内容在多级指针末尾。

函数

函数的定义

`数据类型 函数名([数据类型 形参名,数据类型 形参名...])

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

int main(int argc,char *argv[]) {
//argc终端传递了多少参数,*argv[]参数列表
exit(printf("Hello!\n"));
}

备注
  • 一个进程的返回状态是给父进程看的
  • int argc,char *argv[] main的参数具体意思是命令行传递了多少参数,包括执行命令本身

函数的传参

  • 值传递
  • 地址传递

特别经典的swap()交换函数问题

  • 全局变量

函数的调用

嵌套

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

int max(int a, int b, int c) {
int tmp = a > b ? a : b;
return tmp > c ? tmp : c;
}

int min(int a, int b, int c) {
int tmp = a < b ? a : b;
return tmp < c ? tmp : c;
}

int dist(int a, int b, int c) {
return max(a, b, c) - min(a, b, c);
}

int main() {
printf("%d\n", dist(8, 5, 10));
}

递归

一个函数间接或直接调用自身,这里详细的练习可以去刷算法

斐波那契递归递归实现c
#include <stdio.h>
#include <stdlib.h>

int func(int n) {
if (n < 0) {
return -1; //结束条件
}
if (n == 1 || n == 2) {
return 1;
}
return func(n-1) + func(n-2);
}

int main() {
int n;
scanf("%d", &n);
printf("fib %d = %d", n, func(n));
exit(0);
}

阶乘递归实现c
#include <stdio.h>
#include <stdlib.h>

int func(int n) {
if (n < 0) {
return -1;
}
if (n == 0 || n == -1) {
return 1;
}
return n * func(n - 1);
}

int main() {
int n;
scanf("%d", &n);
printf("%d! = %d", n, func(n));
exit(0);
}

函数与数组

  • 一维数组
用指针传递数组c
#include <stdlib.h>
#include <stdio.h>

// int *arr 的大小是8个字节,看不出数组是多大的,不能使用sizeof判断
void printarr(int *arr, int size) {
for (int i = 0;i < size;i++) {
printf("%d ", *(arr+i));
}
printf("\n");
}

int main() {
int arr[] = {1, 2, 3, 4, 5};
printarr(arr, sizeof(arr)/sizeof(*arr));
}

危险

注意这里的int *arr 的大小是8个字节 是一个普通的指针不是数组,同样的如果定义为int p[]也是代表一个指针而不是整个数组,所以一定要传大小来判断数组大小。

  • 二维数组
指针二维数组传参c
#include <stdlib.h>
#include <stdio.h>

void printarr2(int (*p)[3], int m , int n) {
//这是二级指针,也可以把二维数组当中大的一位数组来使用
for (int i = 0;i < m;i++) {
for (int j =0;j < n;j++) {
printf("%4d ", *(*(p+i)+j));
}
printf("\n");
}
}

int main() {
int arr[][3] = {1, 2, 3, 4, 5, 6};
printarr2(arr, 2, 3);
}

  • 字符数组传参
字符串拷贝c
char *mystrcpy(char *dest, const char * src){
char *ret = dest;
if(dest != NULL && src !=NULL)
while((*dest++ = *src++) != '\0')
return ret;
}
字符串n个前拷贝c
char *mystrncpy(char *dest,const char* src,size_t n){
int i;
for(i = 0;i < n && (dest[i]=src[i]); i++);
//赋值语句的返回值是所赋的值
for(;i<n;i++)
dest[i] = '\0';
return dest
}
备注

(dest[i]=src[i])赋值语句的返回值是所赋的值,当src为空\0的符号,结果返回假。 size_t 是一些C/C++标准在stddef.h中定义的,size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数。

函数的指针

指针函数

返回值为指针的函数

数据类型 * 函数名(形参)

查找二维数组的第几行内容c
#include <stdio.h>
#include <stdlib.h>

#define M 2
#define N 3

int *findnum(int (*p)[], int num) {
if (num > M - 1) {
return NULL;
}
return *(p + num);
}

int main() {
int arr[M][N] = {{1, 2, 3},{ 4, 5, 6}};

int *res = findnum(arr, 1);

for (int i = 0; i < N; i++) {
printf("%d ", *(res + i));
}
}

函数指针

指向函数的指针变量

数据类型 (*指针名)(形参)

int (*p)(int); //一个指针指向函数
函数指针说明c
int add(int a,int b){
return a+b;
}
int (int,int) *p //这里就是函数指针的定义,是抽象概念上的,与函数具体地址无关
//也可以写做
int (*p)(int,int);
p=add; //函数的入口地址
ret = p
提示

int (int,int)这个是以上代码add函数的抽象,是函数的最本质的内容,而函数名(地址常量)只是标识是一段代码所关联的入口地址,但是这个地址是可以变化的。

函数指针数组

指向函数的指针数组

数据类型 (*数组名[下标]) (形参)

int (*funcp[N])(int)

指向指针函数的函数指针数组

int *(*funcp[N])(int)
提示

有了函数名,为什么还要定义指向函数的指针呢? 为了传输函数名过去,这种叫做回调函数

实际例子:

void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);

构造类型

结构体

产生及意义

描述复杂的数据类型

类型描述

建议结构体声明在函数外

struct [node_st] {  //可以没有结构体名字,但是要在定义的时候初始化。
type1 name1;
type2 name2;
...

}; //要以分号结尾
提示

结构体的任何描述不占用空间,只有实例化结构体才开辟空间,实例化后是由按照描述的顺序顺序开辟存储。

嵌套定义

嵌套定义c
#define NAMESIZE 32
struct birthday_st{
int year;
int month;
int day;

};
struct student_st{
int id;
char name[NAMESIZE];
struct birthday_st birth1;
struct birthday_st{ //也可以嵌套描述
int year;
int month;
int day;
}birth2; //请注意变量名
int math;
int chinese;
};
int main()
{
struct student_st stu ={10011,"Alan",{2011,11,11},98,97};
struct student_st stu ={.math = 98,.chinese = 97};
printf("%..%",stu.id,stu.name,stu.birth.year,stu.birth.month,stu.birth.day,stu.math,stu.chinese)
}

定义变量初始化以及成员引用

  • 成员引用:变量名.成员名
  • 结构体指针:指针->成员名(*指针).成员名
变量初始化示例c
struct A {
int i;
char c;
float f;
};

int main() {
// TYPE NAME = VALUE;
struct A a = {123, 'A', 2.22}; // 初始化
struct A a_ = { .c = 'A', .f = 2.22}; // 部分初始化
struct A *ap = { .c = 'A', .f = 2.22}; // 部分初始化

printf("%d %c %.2f\n",a.i, a.c, a.f); // 成员引用
// 123 A 2.22
printf("%d %c %.2f\n",a_.i, a_.c, a_.f); // 成员引用
// 0 A 2.22
printf("%d %c %.2f\n",ap->i, ap->c, ap->f); // 成员引用
// 0 A 2.22

}

占用内存空间大小

结构体对齐

计算各类型在标准结构体中的占用空间:addr % sizeof(type) 如果不能整除的话addr就要继续加一直到整除。

提示

这里涉及到计算机组成原理的存储边界对齐设计:

  • 字地址:4的倍数
  • 半字地址:2的倍数
  • 双字地址:8的倍数
  • 字节地址:任意

  1. 结构体(struct)的数据成员,第一个数据成员存放的地址为结构体变量偏移量为0的地址处.
  2. 其他结构体成员自身对齐时,存放的地址为min{有效对齐值为自身对齐值, 指定对齐值} 的最小整数倍的地址处。注:自身对齐值:结构体变量里每个成员的自身大小
  3. 总体对齐时,字节大小是min{所有成员中自身对齐值最大的, 指定对齐值} 的整数倍。
#include <stdio.h>
#include <stdlib.h>

struct A {
int i;
char c;
float f;
};

// 可以使用下面的方法取消对齐 常用于网络通信
struct B {
int i;
char c;
float f;
}__attribute__((packed));

int main() {
struct A a;
struct B b;

printf("A = %ld\n", sizeof(a));
printf("B = %ld\n", sizeof(b));
}

提示

在进行网络编程时候,为了统一字节流不要出现因为对齐而造成的空格,使用宏__attribute__((packed))指定取消对齐,__attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。

函数传参结构体

  • 直接在函数定义结构体
void func(struct simp_st b); //这个形参的定义会把全部的成员变量传过去

缺点:会把全部的成员变量传过去,是的函数参数消耗大。

  • 把结构体的指针传过去
struct simp_st a;
struct simp_st *p = &a;
void func(struct simp_st *p); //指针的大小都是固定的

优点:节省开销,工程上的应用

结构体应用

结构体学生信息管理c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NAMESIZE 32

struct student_st
{
int id;
char name[NAMESIZE];
int math;
int chinese;
};
void stu_set(struct student_st *p, const struct student_st *q)
{
*p = *q; // 结构体内容可以直接赋值
/*
p->id = q -> id ;
strncpy(P->name,"Alan",NAMESIZE);//因为name不能直接赋字符常量值
p->math = 90;
p->chinese = 98;
*/
}
void stu_show(struct student_st *p)
{
printf("%d %s %d %d\n", p->id, p->name, p->math, p->chinese);
}
void stu_changename(struct student_st *p, const char *newname)
{
strcpy(p->name, newname);
}
void menu(void)
{
printf("1 set\n2 change name\n3 show\n");
}
int main()
{
struct student_st stu, tmp;
int choice;
do
{
menu();
int ret = scanf("%d".& choice);
if (ret != 1)
{
break;
}
switch (choice)
{
case 1:
scanf("%d%s%d%d", &tmp.id, tmp.name, &tmp.math, &tmp.chinese);
stu_set(&stu, &temp);
break;
case 2:
stu_changename(&stu, "wrm244");
break;
case 3:
stu_show(&stu);
break;
default:
exit(1);
}
} while (1);
exit(1);
}

共用体

产生及意义

多个成员共用一块空间,取最大的成员的类型大小作为共用体的类型大小,比如说你的性别可以有男女,但只有一个可选项。

类型描述

union 共用体名{};

union test_un{
int i;
float f;
double d;
char ch;
};
备注

定义赋值之后只对一个成员变量有效,这里的有效指的是有意义。

嵌套定义

同结构体,可以互相嵌套。

union
{
int a;
double d;
struct
{
int srr[10];
float f;
}c;
};

以上代码的占用空间大小为44。

嵌套的应用
  • 判断大小端
  • 32位的数高十六位与第十六位相加
union{
struct
{
uint16_t i;
uint16_t j;
}x;
uint32_t y;
}a;

a.y = 0x11223344;
a.x.i + a.x.j //为答案

定义变量初始化以及成员引用

  • 成员引用:变量名.成员名
  • union指针:指针->成员名(*指针).成员名

32位的无符号数的高16位和低16位相加

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

int main() {
uint32_t i = 0x11223344;
printf("%x\n", (i>>16)+(i&0xFFFF));
}

另一种写法

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

union {
struct {
uint16_t i;
uint16_t j;
}x;
uint32_t y;
}u;

int main() {
uint32_t i = 0x11223344;
printf("%x\n", (i>>16)+(i&0xFFFF));

u.y = 0x11223344;
printf("%x\n", u.x.i + u.x.j);
}

位域

用这个来定义的话,在移殖的时候会冲突

union
{
struct
{
char a:1;
char b:2;
char c:1;
}x;
char y;
}w;

int main()
{ w.y = 1;
printf(w.x.a)
exit(0);
}

枚举

enum 标识符{};

enum 标识符{
成员1;
...
};
enum day {
MON = 1,
TUS,
WEB,
THR,
FRI,
SAT,
SUN,
};

int main() {
enum day a = FRI;

printf("%d\n", a);
}
enum status {
RUNNING = 1,
STOP,
PAUSE,
};

struct job {
int id;
int state;
time_t start, end;
};

int main() {
struct job_st job1;

switch(jobs.state) {
case RUNNING:
// TODO
break;
case STOP:
// TODO
break;
case PAUSE:
// TODO
break;
default:
// TODO
abort();
}
}

typedef

对已有的数据类型进行改名

typedef type typename

typedef int INT

int main() {
INT i = 9;
}
  • typedef 和 define 的区别
#define IP int *
typedef int *IP;

int main() {
// 宏
IP p, q;
int *p, q; // 一个int * 一个int

// typedef
IP p, q;
int *p, *q; // 两个int *
}
  • 数组
typedef int ARR[6]; // int [6] 改名为 ARR

ARR a; // int a[6];
  • 结构体
typedef struct {
int i;
float f;
}NODE, *NODEP;
  • 函数
typedef int *FUNC(int)
  • 函数指针
typedef int* (*FUNCP)(int)

FUNCP P; --> int *(*P)(int);

动态内存管理

malloc

malloc函数解释c
/*
The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
*/
void *malloc(size_t size);
malloc申请示范c
int *p = NULL;
p = malloc(sizeof(int));
if(p == NULL)
exit(1);
free(p);
提示

p = malloc(sizeof(int));这里的语句不用强制转换的(int *)malloc(sizeof(int)),因为他的返回值为void *为百搭类型,但是有些编译器会报不匹配,但基本上是由于你没有包含库#include <stdlib.h>,即编译器没有见到函数原型,编译器会把他当作int类型返回。

实现动态数组

int num = 5; //分配为几个空间
int *p = malloc(sizeof(int)*num);
for(i = 0 ;i<num;i++){
scanf("%d",p++);
}

指针传参导致的内存泄漏

#include <stdio.h>
#include <stdlib.h>
void func(int *p, int n)
{
p = malloc(n); //这里的p是局部变量的p
printf("[%s] p->%p\n",__FUNCTION__,p);
if(p == NULL)
exit(1);
return ;
}

int main()
{
int num =100;
int *p = NULL;
func(p,num);
printf("[%s] p->%p\n",__FUNCTION__,p);
free(p);
exit(0);
}

以上代码的测试结果如下:

[func] p->0x91e2a0
[main] p->(nil)
危险

所以问题就出现在[main]->(nil)main函数中定义了一个空指针,传参过去给func() 函数也是空指针,结果都会导致[func] p->0x1bc76b0申请的地址你找不到,导致内存泄漏。当然如果你在main函数单纯定义了指针的数据类型没有为其开辟属于他的可读空间,结果会导致[func] p->0x1bc76b0指向的空间为只读或者保护内存。

  • 第一种修改办法,传指针的地址给函数:
传指针的地址c
#include <stdio.h>
#include <stdlib.h>
void func(int **p, int n)
{
*p = malloc(n);
printf("[%s] p->%p\n",__FUNCTION__,*p);
if(*p == NULL)
exit(1);
return ;
}

int main()
{
int num =100;
int *p = NULL;
func(&p,num);
printf("[%s] p->%p\n",__FUNCTION__,p);
free(p);
exit(0);
}

/**
运行结果
[func] p->0x6792a0
[main] p->0x6792a0
*/
  • 第二种方法修改办法,要求函数返回指针:
指针函数返回c
#include <stdio.h>
#include <stdlib.h>
void *func(int n)
{
    void *p = malloc(n); //这里的p是局部变量的p
    printf("[%s] p->%p\n",__FUNCTION__,p);
    if(p == NULL)
        exit(1);
    return p;
}
int main()
{
    int num =100;
    int *p = NULL;
    p = func(num);
    printf("[%s] p->%p\n",__FUNCTION__,p);
    free(p);
    exit(0);
}

/*
[func] p->0x1f612a0
[main] p->0x1f612a0
*/
  • 第三种方法,在调用函数前,先申请好指针所需的空间:
在调用函数之前申请空间c
#include <stdio.h>
#include <stdlib.h>
void func(int *p, int n)
{
*p = 2;
printf("[%s] p->%p\n",__FUNCTION__,p);
printf("[%s] *p->%d\n",__FUNCTION__,*p);
if(p == NULL)
exit(1);
return ;
}

int main()
{
int num =100;
int *p = malloc(sizeof(int*));
func(p,num);
printf("[%s] p->%p\n",__FUNCTION__,p);
printf("[%s] *p->%d\n",__FUNCTION__,*p);
free(p);
exit(0);
}
/*
执行结果:
[func] p->0xdb72a0
[func] *p->2
[main] p->0xdb72a0
[main] *p->2
*/
提示

还有一种情况是需要区分的在封装free()函数传值问题,是可以直接传指针的值进去给函数,因为free函数是通过你的地址,把这一块地址值都销毁掉,没有返回值。

int sqlist_destroy(sqlist *me)
{
    printf("[%s] p->%p\n",__FUNCTION__,me);
    printf("[%s] size=%d\n",__FUNCTION__,sizeog(me));
    free(me);
    me = NULL;
    printf("[%s] p->%p\n",__FUNCTION__,me);
    return 0;
}
//...
    printf("%d\n",list->data[1]);
    printf("[%s] p->%p\n",__FUNCTION__,list);
    sqlist_destroy(list);
    printf("[%s] p->%p\n",__FUNCTION__,list);
    printf("%d\n",list->data[1]);
 
 /*输出结果如下
34
[main] p->0x7952a0
[sqlist_destroy] p->0x7952a0
[sqlist_destroy] size=8 //这里仅仅传入一个指针地址消耗小
[sqlist_destroy] p->(nil)
[main] p->0x7952a0
32549 这里数值和前面的不一样说明了该内存已经被释放
这里是直接通过结构体传参导致大小过大,函数消耗过大。
[sqlist_destroy] size=4100
 */

但是这里还是和之前的一样,是把结构体指针传入函数中,减少了传值消耗。

  • calloc
/*
calloc()函数的作用是:为每个大小为字节的nmemb元素数组分配内存,并返回一个指向所分配内存的指针。存储器设置为零。如果nmemb或size为0,则calloc()返回NULL或唯一的指针值,该指针值可以在以后成功传递给free()。如果nmemb与size的乘法将导致整数溢出,则calloc()返回错误。相反,在以下对malloc()的调用中不会检测到整数溢出,因此将分配大小不正确的内存块:malloc[nmemb*size);
*/
void *calloc(size_t nmemb, size_t size);
  • realloc
/*
realloc()函数的作用是:将ptr指向的内存块的大小更改为字节大小。从区域开始到新旧大小的最小值,内容将保持不变。如果新大小大于旧大小,则不会初始化添加的内存。如果ptr为NULL,则对于大小的所有值,该调用等效于malloc(size);如果大小等于零,并且ptr不为NULL,则调用等效于free(ptr)。除非ptr为NULL,否则它必须由之前对malloc()、calloc()或realloc()的调用返回。如果移动了指向的区域,则执行空闲(ptr)。
*/
void *realloc(void *ptr, size_t size);

free

谁申请谁释放

/*The  free() function frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc(), or realloc().   Otherwise,  or if free(ptr) has already been called before, undefined behavior occurs.  If ptr is NULL,  no  operation  is performed.
*/
void free(void *ptr);
  • free深入理解:
free深入理解c
#include <stdlib.h>
#include <stdio.h>

int main() {
  int *p = malloc(sizeof(int));
  *p = 1;
  printf("%p-->%d\n", p,*p);
  free(p); //分配给别人用
  p = NULL;
  *p = 123; //这里有内存泄漏风险
  printf("%p-->%d\n", p,*p);
}
/*
0x1aa82a0-->1
0x1aa82a0-->123
*/
备注

对于以上结果可以看得出free()了的指针指向的地址不变,但是地址中保存的值已经被抛弃了(其他进程可以访问这段内存),所以要保留个习惯,在free()之后让指针指向NULL。所以你再去使用的时候会报段错误 (核心已转储)但是已经比隐含出错更好的结果了。

与结构体结合程序

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

struct student_st
{
int id;
char *name;
int math;
int chinese;
};
void stu_set(struct student_st *p, const struct student_st *q)
{

p->id = q -> id ;
p->name = malloc(strlen(q->name)+1);
if(p->name==NULL)
exit(1);
strcpy(p->name,q->name);
p->math = 90;
p->chinese = 98;

}
void stu_show(struct student_st *p)
{
printf("%d %s %d %d\n", p->id, p->name, p->math, p->chinese);
}
void stu_changename(struct student_st *p, const char *newname)
{
free(p->name); //把原来name之前的值给free
p->name = malloc(strlen(newname)+1);
strcpy(p->name, newname);
}
void menu(void)
{
printf("1 set\n2 change name\n3 show\n");
}
int main()
{
struct student_st stu, tmp;
int choice;
do
{
menu();
int ret = scanf("%d".& choice);
if (ret != 1)
{
break;
}
switch (choice)
{
case 1:
tmp.name = malloc(1024);
scanf("%d%s%d%d", &tmp.id, tmp.name, &tmp.math, &tmp.chinese);
stu_set(&stu, &temp);
break;
case 2:
stu_changename(&stu, "wrm244");
break;
case 3:
stu_show(&stu);
break;
default:
exit(1);
}
} while (1);
exit(1);
}

Makefile

工程管理依赖管理

  • makefile(用户自定义 更高优先级)
  • Makefile(默认)
mytool:main.o tool1.o tool2.o
gcc main.o tool1.o tool2.o -o mytool
main.o:main.c
gcc main.c -c -Wall -g -o main.o
tool1.o:tool1.c
gcc tool1.c -c -Wall -g -o tool1.o
tool2.o:tool2.c
gcc tool2.c -c -Wall -g -o tool2.o

OBJS=main.o tool1.o tool2.o
CC=gcc

mytool:$(OBJS)
$(CC) $(OBJS) -o mytool

main.o:main.c
$(CC) main.c -c -Wall -g -o main.o
tool1.o:tool1.c
$(CC) tool1.c -c -Wall -g -o tool1.o
tool2.o:tool2.c
$(CC) tool2.c -c -Wall -g -o tool2.o

clean:
$(RM) $(OBJS) mytool -r

$^ 表示在上一句依赖关系中被依赖的所有文件 $@ 表示在上一句依赖关系中依赖项的目标文件

CFLAGS=-Wall -g -c
OBJS=main.o tool1.o tool2.o
CC=gcc

mytool:$(OBJS)
$(CC) $^ -o $@

main.o:main.c
$(CC) $^ $(CFLAGS) -o $@
tool1.o:tool1.c
$(CC) $^ $(CFLAGS) -o $@
tool2.o:tool2.c
$(CC) $^ $(CFLAGS) -o $@

clean:
$(RM) $(OBJS) mytool -r

% 表示同一个名字

CFLAGS=-Wall -g -c
OBJS=main.o tool1.o tool2.o
CC=gcc

mytool:$(OBJS)
$(CC) $^ -o $@

%.o:%.c
$(CC) $^ $(CFLAGS) -o $@

clean:
$(RM) $(OBJS) mytool -r