系统相关
首页 > 系统相关> > 动态内存管理、文件操作、预处理

动态内存管理、文件操作、预处理

作者:互联网

上一篇

目录标题

动态内存管理

通过创建变量的方式来申请内存的.啥时候释放内存,就得看变量是啥样的变量了.
如果是全局变量,就跟随程序释放.
如果是静态变量,也跟随程序释放.
如果是局部变量,就跟随代码块释放.

1.动态内存管理,能够更灵活的决定申请时机和释放时机~
2.动态内存管理可以在运行时决定内存申请的大小~

malloc

void* malloc (size_t size);
//size字节数,编译器只知道内存起始地址,
//不知道大小,申请连续内存,
//正常得情况下要检查是否申请成功(不成功返回NULL)

在这里插入图片描述

使用

#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
	int* p = (int*)malloc(4);
	*p = 10;
	printf("%d\n", *p);

	 //把这 40 个字节理解成一个 长度 10 个元素的 int 数组
	// p 就当成了数组的起始元素地址. 
	int* p = (int*)malloc(10 * sizeof(int));

	for (int i = 0; i < 10; i++) {
		p[i] = 0;
	}
	system("pause");
	return 0;
}

释放时间

1.程序如果运行结束,就跟随程序一起释放了.
2.程序没结束的时候,手动调用free ,也就释放了.

free

void free (void* ptr);//ptr maolloc返回的地址,可以处理NULL

使用

#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
	 //把这 40 个字节理解成一个 长度 10 个元素的 int 数组
	// p 就当成了数组的起始元素地址. 
	int* p = (int*)malloc(10 * sizeof(int));

	for (int i = 0; i < 10; i++) {
		p[i] = 0;
	}
	free(p);
	system("pause");
	return 0;
}

在C++里面,引入了"智能指针”,就能一定程度的解决内存泄露问题.利用了C++中的RAlI机制~就能够在函数退出之前,执行一些逻辑.
就可以把free操作放到这样的逻辑之中~
Java / Python,引入了垃圾回收机制,就能更好的解决内存泄露问题.
内存的回收不必由程序猿手动回收了,而是程序内部,有专门的逻辑来定期扫描,看看当前哪些内存是可以释放然后就自动释放了.

calloc

void* calloc (size_t num, size_t size);

使用

#include <stdio.h>
#include <stdlib.h>
int main()
{
 int *p = (int*)calloc(10, sizeof(int));
 if(NULL != p)
 {
 //使用空间
 }
 free(p);
 p = NULL;
 return 0; }

calloc申请到的内存,会被初始化为全0.
malloc申请到的内存,则不会初始化,内存上的值都是随机值.

realloc

realloc扩容的时候,也是看后面的空余空间够不够~如果后面的空间够,就直接把墙打通,直接利用后面的空间.
如果后面的空间不够,就直接找一个更大的空间,整体搬运过去~

void* realloc (void* ptr, size_t size);

使用

#include <stdio.h>
int main()
{
 int *ptr = (int*)malloc(100);
 if(ptr != NULL)
 {
     //业务处理
 }
 else
 {
     exit(EXIT_FAILURE);    
 }
 //扩展容量
 //代码1
 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
 
 //代码2
 int*p = NULL;
 p = realloc(ptr, 1000);
 if(p != NULL)
 {
 ptr = p;
 }
 //业务处理
 free(ptr);
 return 0; }

常见的内存错误

void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//如果p的值是NULL,就会有问题
 free(p);
}

void test()
{
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
}

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//只释放动态申请的内存
}

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}

void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }//内存泄漏
}
int main()
{
 test();
 while(1);
}

常见的题型

1.

void GetMemory(char *p) {
 p = (char *)malloc(100);
}
void Test(void) {
 char *str = NULL;
 GetMemory(str);
 strcpy(str, "hello world");
 printf(str);
}

1.内存泄露. malloc没有free
2. malloc之后没有判定返回值为NULL
3.GetMemory函数并不能修改str.的值~str 仍然是 NULL.后面进行strcpy,就会出现问题~

2.

char *GetMemory(void) {
 char p[] = "hello world";//局部变量随着函数释放而释放
 return p; }
void Test(void) {
 char *str = NULL;
 str = GetMemory();//局部变量所指的内存已经释放
 printf(str);
}

3.

void GetMemory(char **p, int num) {
 *p = (char *)malloc(num);
}
void Test(void) {
 char *str = NULL;
 GetMemory(&str, 100);
 strcpy(str, "hello");
 printf(str);
}

1.内存泄露. malloc没有free
2. malloc之后没有判定返回值为NULL

4.

void Test(void) {
 char *str = (char *) malloc(100);
 strcpy(str, "hello");
 free(str);
 if(str != NULL)
 {
 strcpy(str, "world");
 printf(str);
 }
}

在这里插入图片描述

一个程序的内存区域

linux中
在这里插入图片描述

在这里插入图片描述
栈的大小可以配置(一般1m)

linux修改栈的大小
在这里插入图片描述

文件

分类

普通文件:文本文件,二进制文件
目录文件

相关函数

io函数都在stdio.h中包含

FILE结构体

用来描述一个文件~
文件是在磁盘上的.要想直接操作磁盘,不太容易.因此操作系统就进行了封装.
打开文件的时候,其实就是在内存中创建了一个变量(FILE结构体变量),这个变量就和磁盘上的文件关联起来了.读/写这个变量,也就相当于在读写文件了.

fopen

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//参数前者文件路径,后者打开方式

在这里插入图片描述

使用

#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
	/* fp => file pointer 文件指针
		 如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/ 
		FILE* fp = fopen("E:/test.txt", "r");
		if (fp == NULL) {
			printf("打开文件失败! 错误码: %s\n", strerror(errno));//error一个宏定义代表错误
			system("pause");
			return 0;
		}
		printf("打开文件成功! fp=%p\n", fp);
	system("pause");
	return 0;
}

在这里插入图片描述

strerror

返回错误码,所对应的错误信息。

char * strerror ( int errnum );

使用

#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件
int main ()
{
  FILE * pFile;
  pFile = fopen ("unexist.ent","r");
  if (pFile == NULL)
    printf ("Error opening file unexist.ent: %s\n",strerror(errno));
    //errno: Last error number
  return 0; }

fclose

//关闭文件
int fclose ( FILE * stream );

使用

#include <stdio.h>
int main ()
{
  FILE * pFile;
  //打开文件
  pFile = fopen ("myfile.txt","w");
  //文件操作
  if (pFile!=NULL)
 {
    fputs ("fopen example",pFile);
    //关闭文件
    fclose (pFile);
 }
  return 0; }

fread

数据读取方式:字节流和数据报

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//*ptr读到内容的内存地址,返回值读取成功的个数

使用

#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
	/* fp => file pointer 文件指针
		 如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/ 
		FILE* fp = fopen("E:/test.txt", "r");
		if (fp == NULL) {
			printf("打开文件失败! 错误码: %s\n", strerror(errno));//error一个宏定义代表错误
			system("pause");
			return 0;
		}
		printf("打开文件成功! fp=%p\n", fp);
		char buffer[10] = "1111" ;
		fread(buffer, 1, 8, fp);
		printf(buffer);
	system("pause");
	return 0;
}

fwrite

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

使用

#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
	/* fp => file pointer 文件指针
		 如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/ 
		FILE* fp = fopen("E:/test.txt", "w");
		if (fp == NULL) {
			printf("打开文件失败! 错误码: %s\n", strerror(errno));//error一个宏定义代表错误
			system("pause");
			return 0;
		}
		printf("打开文件成功! fp=%p\n", fp);
		char buffer[10] = "1111" ;
		fwrite(buffer, 1, 4, fp);
		printf(buffer);
	system("pause");
	return 0;
}

其他顺序读写

在这里插入图片描述

EOF
如果一个文件已经被读完了,然后再来继续尝试读,就会读到EOF.
EOF '实际上是-1,并不是一个ascii码表中的字符.只是使用-1这个数据来代表文件读取完毕.

sscanf和sprintf

//sscanf从一个字符串里解析出一个内容.
//sprintf 把一个格式化结果输出到一个字符串中.
	char* str = "num = 10";
	int num = 0;
	sscanf(str, "num = %d", &num);
	printf("%d\n", num);
	char str[1024] = { 0 };
	sprintf(str, "num = %d", 10);
	printf(str);


一个特别重要的用法://针对字符串和数字(整数/浮点数)之间进行转换~
// 把字符串转成整数, 使用 sscanf
		char* str1 = "10";
		char* str2 = "20";
		// 此处想计算这两个值相加~ 

		int num1 = 0;
		// 这个时候就是把 str1 中的内容提取出来, 转成了 int
		sscanf(str1, "%d", &num1);
		int num2 = 0;
		sscanf(str2, "%d", &num2);
		int result = num1 + num2;
		printf("result = %d\n", result);


// 把整数转成字符串, 使用 sprintf
	int num = 100;
	char buffer[1024] = { 0 };
	sprintf(buffer, "%d", num);
	printf("%s\n", buffer);

整型和字符串的转换 atoi iota

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>     /* atoi */

int main ()
{
	int i;
	char buffer[256];
	printf("Enter a number: ");
	fgets(buffer, 256, stdin);
	i = atoi(buffer);
	printf("The value entered is %d. Its double is %d.\n", i, i * 2);
	system("pause");
	return 0;

}



char *  itoa ( int value, char * str, int base );

#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>     /* atoi */

int main ()
{
	int i;
	char buffer[33];
	printf("Enter a number: ");
	scanf("%d", &i);
	_itoa(i, buffer, 10);
	printf("decimal: %s\n", buffer);
	_itoa(i, buffer, 16);
	printf("hexadecimal: %s\n", buffer);
	_itoa(i, buffer, 2);
	printf("binary: %s\n", buffer);
	system("pause");
	return 0;

}

fscanf

int fscanf ( FILE * stream, const char * format, ... );

使用

/* fscanf example */
#include <stdio.h>

int main ()
{
  char str [80];
  float f;
  FILE * pFile;

  pFile = fopen ("myfile.txt","w+");
  fprintf (pFile, "%f %s", 3.1416, "PI");
  rewind (pFile);
  fscanf (pFile, "%f", &f);
  fscanf (pFile, "%s", str);
  fclose (pFile);
  printf ("I have read: %f and %s \n",f,str);
  return 0;
}

fprintf

int fprintf ( FILE * stream, const char * format, ... );

使用

#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
	/* fp => file pointer 文件指针
		 如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/ 
		FILE* fp = fopen("E:/test.txt", "w");
		if (fp == NULL) {
			printf("打开文件失败! 错误码: %s\n", strerror(errno));//errno一个宏定义代表错误
			system("pause");
			return 0;
		}
		printf("打开文件成功! fp=%p\n", fp);
		char buffer[10] = "1111" ;
		fprintf(fp,"jiezhe:%s",buffer);
		printf(buffer);
	system("pause");
	return 0;
}
// stdout 其实就是一个 FILE* 类型的变量. 这个就是咱们所说的标准输出. 
	// 这个代码就等价于 printf 
	 fprintf(stdout, "num = %d\n", 10);
	// 类似的, fscanf(stdin, "xxxxx"); 这个代码就等价于 scanf 

随机读写

fseek

根据文件指针的位置和偏移量来定位文件指针

int fseek ( FILE * stream, long int offset, int origin );

#include <stdio.h>

int main ()
{
  FILE * pFile;
  pFile = fopen ( "example.txt" , "wb" );
  fputs ( "This is an apple." , pFile );
  fseek ( pFile , 9 , SEEK_SET );
  fputs ( " sam" , pFile );
  fclose ( pFile );
  return 0;
}

ftell

返回文件指针相对于起始位置的偏移量

long int ftell ( FILE * stream );

#include <stdio.h>
int main ()
{
  FILE * pFile;
  long size;
  pFile = fopen ("myfile.txt","rb");
  if (pFile==NULL) perror ("Error opening file");
  else
 {
    fseek (pFile, 0, SEEK_END);   // non-portable
    size=ftell (pFile);
    fclose (pFile);
    printf ("Size of myfile.txt: %ld bytes.\n",size);
 }
  return 0; }

rewind

让文件指针的位置回到文件的起始位置

void rewind ( FILE * stream );


#include <stdio.h>
int main ()
{
  int n;
  FILE * pFile;
  char buffer [27];
  pFile = fopen ("myfile.txt","w+");
  for ( n='A' ; n<='Z' ; n++)
    fputc ( n, pFile);
  rewind (pFile);
  fread (buffer,1,26,pFile);
  fclose (pFile);
  buffer[26]='\0';
  puts (buffer);
  return 0; }

文件读取结束的判定

  1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
    例如:
    fgetc 判断是否为 EOF .
    fgets 判断返回值是否为 NULL .
  2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
    例如:
    fread判断返回值是否小于实际要读的个数。

刷新缓存区

#include <stdio.h>
#include <windows.h>
//VS2013 WIN10环境测试
int main()
{
 FILE*pf = fopen("test.txt", "w");
 fputs("abcdef", pf);//先将代码放在输出缓冲区
 printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
 Sleep(10000);
 printf("刷新缓冲区\n");
 fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
 //注:fflush 在高版本的VS上不能使用了
 printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
 Sleep(10000);
 fclose(pf);
 //注:fclose在关闭文件的时候,也会刷新缓冲区
 pf = NULL;
 return 0; }

刷新缓存区时间:
1.缓冲区满了.
2. fclose关闭了.
3. fflush这个函数的功能就是手动刷新缓冲区.

预处理

2编译过程最重要
在这里插入图片描述

预定义符号

__FILE__      //进行编译的源文件
__LINE__     //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

define

1.定义常量~

#define MAX 1000

2.给类型定义别名~

#define uint unsigned int
uint num = 10;

3.自定义一些"关键字"|

#define and &&
	#define 并且 &&
	#define 如果 if
	#define 否则 else
	#define 循环 for
	#define 赋值 =
	#define 整型 int
	int num = 10;
	if (num < 10 并且 num > 0) {

	}
	整型 num 赋值 10;
	如果(num < 10 并且 num > 0) {

	} 否则 {

	}

4.通过宏作为一些"编译开关”(结合者#if这系列操f作)

5.定义一个代码片段.

#define ADD(x, y) x + y
printf("%d\n", ADD(10, 20));

宏优势和问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.宏不可递归
4.宏没有参数检查.(参数不写类型,看起来好像是方便了,但实际上反而是更麻烦了.参数没有检查了,你实际参数传的对不对,是否符合要求,都不得而知了)|

#undef

这条指令用于移除一个宏定义。

条件编译

1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
2.多个分支的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
 #ifdef OPTION1
 unix_version_option1();
 #endif
 #ifdef OPTION2
 unix_version_option2();
 #endif
#elif defined(OS_MSDOS)
 #ifdef OPTION2
 msdos_version_option2();
 #endif
#endif

应用:
1.兼容开发和发布环境,
在这里插入图片描述
2.兼容多系统,
3.防止头文件重复包含(#pragma once 在定义处使用),
4.实现多行注释,如下
在这里插入图片描述
5.支持嵌套
在这里插入图片描述

标签:文件,NULL,pFile,int,char,动态内存,printf,include,预处理
来源: https://blog.csdn.net/feiqipengcheng/article/details/122244812