文章目录

内存泄露和野指针出现的可能原因?

内存泄露:
1.未正确使用free函数:在动态分配内存后,如果忘记调用free函数来释放内存,会导致内存泄露。
2.误用指针:将指针指向新的内存块后,如果没有释放之前分配的内存,会导致内存泄露。
3.指针丢失:如果保存了某个指针的地址,但后续无法再找到这个指针来释放内存,也会导致内存泄露。

野指针
1.指针未初始化:声明指针变量但没有初始化,会导致指针指向未知的地址。
2.指针指向已释放的内存:如果指针指向的内存被释放后,没有将指针置为NULL,会导致野指针。
3.函数返回局部变量的地址:如果将函数内部的局部变量地址返回,但该局部变量已经被销毁,会导致野指针

C语言整数自动转换原则

当表达式中存在有符号类型和无符号类型时所有的数都自动转换为无符号类型。

void foo(void)
{
	unsigned int a = 6;
	int b = -20;
	(a+b> 6)? puts("> 6") : puts("<= 6");
}

上述代码中-20 变成了一个非常大的正整数,所以该表达式计算出的结果大于6 。
printf(“b=%u\n”, b); //b=4294967276
a+b = 6+(-20) = 6 + 4294967276 = 4294967282

介绍一下堆栈

  1. 存储内容不同

栈:在函数调用时,栈中存放的是函数中各个参数(局部变量)。栈底下是函数调用后的下一条指令。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

  1. 管理方式上不同

栈:由系统自动分配空间,同时系统自动释放空间。例如,声明在函数中一个局部变量“int b“。系统自动在栈中为b开辟空间,当对应的生存周期结束后栈空间自动释放。
堆:需要程序员手动申请并且手动释放,并指明大小。在C语言中malloc函数申请,释放free函数,在C++中new和delete实现。

  1. 空间大小不同

栈:获取空间较小。在Windows下,一般大小是1M或2M,当剩余栈空间不足时,分配失败overflow。
堆:获得空间根据系统的有效虚拟内存有关,比较灵活,比较大。

  1. 能否产生碎片不同

栈:不会产生碎片,空间连续。
堆:采用的是链表的存储方式,会产生碎片。

  1. 生长方向不同

栈:向低地址扩展的数据结构,是一块连续的内存的区域。
堆:向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。

  1. 分配方式不同

栈:有2种分配方式——静态分配和动态分配。静态由编译器完成,例如局部变量;动态由alloca函数实现,并且编译器会进行释放。
堆:都是动态分配的,没有静态分配的堆。

  1. 分配效率不同

栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来方便。

总的来说就是栈存储的数据又系统决定,堆就比较随意。栈时由系统自动分配释放,堆是由开发人员进行分配释放。栈的可获取空间较小,堆可获取的大小较大,但堆的分配效率较低。由于栈先入后出的特性,使得它不会出现内存碎片。

结构体大小计算

**这个问题的关键是理解==字节对齐==**看视频比文字更加形象,直接看视频吧。

typedef union {
	long i; 
	int k[5]; 
	char c;
} DATE;

struct data{ 
	int cat; 
	DATE cow; 
	double dog;
}too;
DATE max;
printf("%d",sizeof(struct date)+sizeof(max)); 

DATE是一个union,变量公用空间。里面最大的变量类型是int[5],占用20个字节。所以它的大小是20。data 是一个struct,每个变量分开占用空间。依次为int4 + DATE20 + double8 = 32。所以结果是20 + 32 = 52。

更多例子看这篇blog

c语言中什么是内联函数,它有什么特点?

在C语言中,内联函数是指将函数的代码直接嵌入到调用该函数的地方,以避免函数调用的开销。这种优化方法被称为“内联扩展”。内联函数的特点包括:

  1. 提高程序的执行效率:由于内联函数的代码被直接嵌入到调用它的地方,避免了函数调用的开销,因此在一些需要频繁调用的函数中使用内联可以提高程序的执行效率。
  2. 减少函数调用的开销:内联函数的代码被直接嵌入到调用它的地方,因此不需要进行参数传递和返回值处理,减少了函数调用的开销。
    编译器自动优化:内联函数的使用是由编译器自动优化的,编译器会根据函数的调用次数、函数体的大小等因素来决定是否使用内联扩展。
  3. 可能出现空间和时间上的开销:内联函数的代码被直接嵌入到调用它的地方,因此可能会出现代码膨胀的情况,增加了程序的空间开销。同时,由于内联函数的代码被直接嵌入到调用它的地方,也可能会增加程序的执行时间。

需要注意的是,内联函数并不是在所有情况下都是最优的选择,具体是否使用内联函数需要根据实际情况进行权衡。

不同数据如何比较?float、char、bool

//浮点型------0.00001
const float EPISNON = 0.00001;
if((x>EPISNON) || (x<EPISNON))
{
	//......
}
//指针-----NULL
char *p;
if(p!=NULL)
if(p==NULL)
// boll------!
if(x)
if(!x)

c语言中动态库和静态库有什么区别?

在C语言中,动态库和静态库是两种不同的库文件,它们的主要区别在于编译时和运行时的处理方式不同。

静态库(Static Library)是一种在编译时链接的库文件,它包含了一组预编译的目标文件(通常是.o或.obj文件)。在编译时,静态库会被链接到可执行文件中,并成为可执行文件的一部分。因此,静态库在运行时不再需要额外的库文件支持。静态库的主要优点是编译后的可执行文件可以独立运行,不再依赖外部库文件。但是,静态库的缺点是会增加可执行文件的大小,并且如果静态库更新,需要重新编译链接所有使用了该库的可执行文件。

动态库(Dynamic Library)是一种在运行时链接的库文件,它包含了一组可重用的代码和数据。在编译时,动态库并不会被链接到可执行文件中,而是在运行时由操作系统动态加载到内存中。因此,动态库在运行时需要额外的库文件支持。动态库的主要优点是可以减少可执行文件的大小,并且如果多个程序使用了同一个动态库,它们可以共享该库的代码和数据。但是,动态库的缺点是需要额外的运行时开销,并且如果动态库更新,需要重新部署所有使用了该库的程序。

什么是交叉编译?为什么需要交叉编译?

交叉编译是指在一个平台上生成另一个平台上的可执行代码。简单来说,就是将源代码编译成适应不同体系结构或操作系统的目标代码。对应的还有本地编译,本地编译就是在编译平台下编译的代码只能在本平台进行运行。交叉编译是为了适应不同平台主频、内存等等之间的差异而产生的。

交叉编译需要面临的困难有不同体系架构之间不同的机器特性,例如
word size、

交叉编译的出现主要是为了满足特定需求,例如在嵌入式系统开发中,目标系统可能内存较小、显示设备简陋甚至没有,无法在其上进行本地编译,或者编译所需的编译器无法在目标系统上运行。另外,如果目标平台的CPU架构或操作系统与源平台不同,也需要进行交叉编译。

要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(cross compilation tool chain),然后用这个交叉编译工具链编译源代码,最终生成可在目标平台上运行的代码。

c语言指针和存储相关知识?

char *m=0x4000;
int  *n=0x8000;
*(m+1)  //这指向的是哪个地址
*(n+1)  //这又指向哪个地址
int *p=0x0000;
int *q=0x0004;
int *n=0x0005;
*p=0x12345678;
//问此时0x0000~0x0003中存储的数据是什么?
*n=0x12345678;
//问此时0x0004~0x0007中存储的数据是什么?

c语言编译过程?

1)预处理:宏定义展开(#define)、头文件展开(#include)、条件编译(#ifdef #ifndef #endif)等,同时将代码中的注释删除,这里并不会检查语法

2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去
.c转.i .i转.s .s转.o .o转可执行文件,这个可执行文件在windows中通常是.exe

假设我们有一个hello.c文件,则在Linux 下GCC 编译器编译过程为
1、预处理
gcc -E hello.c -o hello.i
2、编译
gcc -S hello.i -o hello.s
3、汇编
gcc -c hello.s -o hello.o
4、链接
gcc hello.o -o hello

void *malloc( size_t size );其中void *表示什么

表示返回指向 void 的指针,可以转换为任何数据类型

c语言中变量声明有哪些?它们又有什么区别?

在C语言中,变量声明的方式主要有以下几种:

  1. 静态变量声明:使用static关键字声明的变量为静态变量。静态变量在程序运行期间一直存在,其生命周期为整个程序运行期间。静态变量在编译时被分配内存,并在程序运行期间一直存在,直到程序结束才被释放。静态变量在函数调用时也可以保持其值。
  2. 全局变量声明:在函数外部声明的变量为全局变量。全局变量在程序运行期间一直存在,其生命周期为整个程序运行期间。全局变量在编译时被分配内存,并在程序运行期间一直存在,直到程序结束才被释放。全局变量在函数调用时也可以保持其值。
  3. 局部变量声明:在函数内部声明的变量为局部变量。局部变量的生命周期只在函数执行期间存在,当函数执行结束后,局部变量被释放。局部变量在运行时被分配内存,并只在函数执行期间存在。
  4. 寄存器变量声明:使用register关键字声明的变量为寄存器变量。寄存器变量的生命周期只在函数执行期间存在,当函数执行结束后,寄存器变量被释放。寄存器变量存储在CPU寄存器中,访问速度比内存快,适用于需要频繁访问的变量。
  5. 外部变量声明:使用extern关键字声明的变量为外部变量。外部变量的生命周期和全局变量相同,但可以在不同的文件中进行声明和定义。外部变量在编译时被分配内存,并在程序运行期间一直存在,直到程序结束才被释放。 _
    我觉得使用extern声明的变量不会再分配内存了,它只是说明了某一个全局变量来自外部文件。_

这些变量声明的区别主要在于变量的生命周期和作用域不同。静态变量和全局变量的生命周期为整个程序运行期间,而局部变量的生命周期只在函数执行期间存在。寄存器变量的存储位置不同,访问速度更快。外部变量可以在不同的文件中进行声明和定义。

#define和const声明的常量有什么区别?

  1. 是否分配内存: const声明的常量可以通过指针间接修改(会有编译的时候会有警告),在C语言中,#define是预处理指令,常用于定义宏。当编译器在预处理阶段遇到#define定义的宏时,它会用定义的内容进行替换,但不会为这些内容分配内存。
  2. const定义的常量是有数据类型的,而define定义的常量只是简单的文本替换,没有数据类型。
  3. const常量具有作用域,只在定义它的作用域内有效,而define常量没有作用域,可以在文件的任何地方使用。
  4. const常量在编译时进行类型检查,可以发现一些隐含的错误,而define常量只是简单的文本替换,不会进行类型检查。
    _需要注意的是

提一个小问题?既然不分配内存,那数据保存在哪里?

  在C和C++中,#define定义的常量实际上并没有被保存在内存中的某个特定位置。#define是预处理器指令,它会在编译前处理阶段把代码中的所有宏名替换为预定义的值。这个过程是在编译时期进行的,而不是在程序运行时期。因此,从某种角度来看,你可以说这些常量被“保存”在了编译后的二进制文件中。

  在程序运行时期,这些常量值会被加载 到程序的内存空间中,但它们并不占据运行时栈或堆空间,而是存储在只读的数据段(通常是.rodata段)中。

  注意,#define不仅仅可以定义常量,还可以定义宏函数。对于宏函数,预处理器会在调用点展开宏函数,而不是像普通函数那样在运行时期调用。这也是预处理器和编译器的优化方式之一。

出一个关于#define的小问题?
#define LENGTH1 10和#define LENGTH2 5+5的区别

#include <stdio.h>
 
#define LENGTH1 10   
#define LENGTH2 5+5

int main()
{
   int area1, area2;  
   area1 = LENGTH1 * 2;
   area2 = LENGTH2 * 2;
   printf("value of area1 : %d", area1);		// 10*2 = 20
   printf("value of area2 : %d", area2);		// 5+5*2=15

   return 0;
}

strlen()和sizeof()的区别?

  1. strlen(): 这是一个函数,通常用于计算字符串(以 null 结尾的字符数组)的长度。strlen() 函数从字符串的开头开始,逐个计数字符,直到遇到结束字符 ‘\0’,然后计算出字符串的长度。例如,对于字符串 “hello”,strlen() 将返回 5,因为它计算的是字符 ‘h’, ‘e’, ‘l’, ‘l’, ‘o’ 的数量。
  2. sizeof(): 这是一个运算符,用于计算变量、类型或数据结构的大小(以字节为单位)。sizeof() 并不计算字符串的长度,而是返回数据类型或对象的大小。例如,sizeof(int) 在大多数系统中将返回 4,因为 int 类型通常占用 4 字节。如果你有一个指向字符串的指针,sizeof() 将返回指针本身的大小,而不是字符串的长度。

总结:strlen()是一个函数,sizeof()是一个关键词。strlen()在计算字符串长度的时候不包括“\0”,而sizeof()包括。

char str[] = "hello";
char *p = str;
int n = 10;

sizeof(str);  //6
strlen(str);  //5
sizeof(p);    //4
sizeof(n);    //4

sizeof(str):这里的str是一个字符数组,其中包含了字符串"hello"的5个字符(包括结尾的\0字符)。因此,sizeof(str)返回的结果是5,表示该数组所占用的字节数。
sizeof§:这里的p是一个字符指针,它指向了str数组的首地址。在32位系统中,一个指针变量所占用的字节数为4;在64位系统中,一个指针变量所占用的字节数为8。但是,由于p指向的是str数组的首地址,因此sizeof§返回的结果是4,表示该指针变量所占用的字节数。
sizeof(n):这里的n是一个整型变量,它的值为10。在大多数系统中,一个整型变量所占用的字节数为4。因此,sizeof(n)返回的结果是4,表示该整型变量所占用的字节数。

//再来几个例子
char  str[] = “Hello”; 			sizeof (str) =6                       
char  *p = str; 				sizeof ( p) =4                                   
int   n = 10;					sizeof (n) =4     
void Func ( char str[100]) {  }	sizeof(str) = 4    
void * p = malloc( 100 );		sizeof (p) = 4

memcpy()和strcpy()的区别是什么?

memcpy()和strcpy()都是C语言中的标准库函数,用于复制内存内容,但它们之间存在一些重要的区别。

  1. 参数不同:

strcpy()的原型是char *strcpy(char *dest, const char *src);它用于将src指向的C字符串(包括终止符\0)复制到dest指向的位置。
memcpy()的原型是void *memcpy(void *dest, const void *src, size_t n);,它用于将src指向的内存块的n个字节复制到dest指向的位置。memcpy()可以处理任何类型的数据,而不仅仅是字符串。

  1. 复制内容不同:

strcpy()只会复制字符串,直到遇到\0为止。如果源字符串的长度超过目标字符串的长度,它可能会导致缓冲区溢出。
memcpy()会复制指定数量的字节,它可以用来复制任何类型的数据,包括字符串。如果源和目标区域重叠,memcpy()的行为是未定义的,可能会导致数据损坏。

  1. 返回值不同:

strcpy()返回目标字符串的指针。
memcpy()返回目标内存区域的指针。

总的来说,strcpy()主要用于字符串的复制,而memcpy()用于更通用的内存块复制。在处理字符串时,如果源和目标区域重叠,应该使用memmove()函数,而不是memcpy(),以避免未定义的行为。

break和continue的区别?

全局变量与局部变量在内存中的区别?

全局变量保存在内存的全局存储区中,占用静态的存储单元;

局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

C语言存储管理?

C语言中的存储管理主要涉及到内存分配和释放。在C语言中,内存分为四个区域:堆区(heap)、栈区(stack)、全局/静态存储区(global/static storage)、常量存储区(constant storage)。

  1. 堆区:由malloc、calloc和realloc等函数分配和释放,这部分内存的分配和释放由程序员控制,不过如果没有正确释放,可能会导致内存泄漏。
  2. 栈区:由编译器自动分配和释放,存放函数的参数值、局部变量等。其操作方式类似于数据结构中的栈。
  3. 全局/静态存储区:存放全局变量、静态变量和常量,程序结束后由系统释放。
  4. 常量存储区:存放常量,不允许修改。
  5. 程序代码区:存放函数体的二进制代码

static的作用

关键字static有三种作用:

  1. 在函数内部:将某一变量声明为静态变量,函数调用结束该变量内存也不会被释放,直到程序结束。
  2. 在函数外部(某个.c文件内):被该关键字声明过的变量可以被当前文件内的函数访问及使用,但是不能被其它文件内的函数访问和使用。
  3. 修饰函数:一个被声明为静态的函数只能被这一文件内的其它函数调用,不能被其它文件的函数调用。

volatile

关键字是C和C++编程语言中的一个修饰符,用于表示一个变量可能会被多个线程或外部事件(如硬件中断)以不可预测的方式访问和修改。关键字的作用是提醒编译器不要对该变量进行任何优化,确保每次访问该变量时都从内存中读取,每次修改该变量时都将数据写回内存。

关键字的主要作用如下:

• 防止编译器优化:变量会告诉编译器不要对该变量进行优化,例如避免编译器将变量值存储在寄存器中而不是内存中,或者避免编译器对多次访问变量的代码进行优化。

• 确保内存访问顺序:变量可以确保在多线程或中断环境下,变量的访问顺序与代码中的顺序一致,从而避免潜在的访问冲突。

• 用于内存映射硬件寄存器:在嵌入式系统开发中,关键字常用于表示内存映射的硬件寄存器。由于硬件寄存器的值可能在任何时候发生变化(例如,由硬件事件触发),使用关键字可以确保编译器生成正确的访问代码。

register关键字的作用

在C语言中,register是一个关键字,用于指定局部变量的存储类别,具体作用如下:
• register修饰的变量是局部变量,默认的存储类别是auto,可以省略不写。
• register变量通常被存储在CPU的寄存器中,以提高变量的访问速度。
• register变量的地址不能被取得,因此不能对其进行取地址操作。
• register变量的数量和大小都是有限制的,取决于CPU的寄存器数量和大小。

需要注意的是register不能修饰全局变量,因为全局变量是存储在内存中的,如果你用register修饰全局变量编译器可能会忽略而不会报错。

inline关键词的作用

inline是C\C++语言中的一个关键字,用于修饰函数,在编译器编译时将函数调用处直接展开为函数体,从而避免了函数调用的开销,提高程序的运行速度。具体作用如下:
• inline修饰的函数在编译时将被直接展开为函数调用处的代码,从而避免了函数调用的开销,提高程序的运行速度。
• inline函数一般都定义在头文件中,可以被多个源文件调用,不会引起重复定义的错误。
• inline函数不能使用递归调用,也不能包含复杂的循环结构或switch语句等。

C语言头文件中是否可以定义一个变量?

可以但是不推荐,推荐的方法是在源文件中定义变量,如果需要在外部文件中调用这个变量,则需要使用extern声明这个变量是一个外部变量。即源文件中定义,头文件中声明

#define和typedef区别

#define用来定义宏,是对文本进行简单的替换;typedef用来定义新的类型名,可以对类型进行封装和抽象。
#define定义的宏没有类型检查和作用域限制,可以定义在任何位置;typedef定义的新类型是有类型的,需要在定义后才能使用。

#define的用处

  1. 定义常量 eg:#define PI 3.14159
  2. 定义函数宏:eg:#define SQUARE(x) ((x)*(x))
  3. 条件编译: eg:#define DEBUG

#define的缺点

  1. 无法进行类型检查

宏定义是在编译前进行字符的替换,因为还没有编译,不能编译前就检查好类型是否匹配,而只能在编译时才知道,所以不具备类型检查功能。

  1. 需要特别注意给各个变量加括号

由于宏定义的时候,其各个分量未加括号,而在使用宏定义的时候,传递的参数是变量的表达式,然后经过系统展开后,由于优先级的原因,导致其结果不是你所希望的。特殊情况时候,加了括号也无法避免错误(在宏定义中出现++或–之类的操作符的时候)

#include <file.h> 和 #include "file.h"的区别

前者是从Standard Library的路径寻找和引用file.h,后者是从当前工作路径搜寻并引用file.h

堆栈溢出的情况有哪些?

  1. 资源申请太多
  2. 没有释放
  3. 递归层次太多

if判断语句中是否可以完成赋值,赋值后if是true还是false??eg:if(a=5) -----> a=5?

运行以下程序,输出是什么?

#include <stdio.h>

int main()
{
	int i=5;
	if(i=0)
		printf("a=%d\r\n",i-10);
	else
		printf("a=%d\r\n",i++);
	return 0; 
}

这是一个大坑呀,我之前没注意这个问题,笔试的时候直接错了。这程序的输出是a=0;
首先我对标题进行回答:if语句中的判断语句可以使用赋值语句,且能够生效,也就是a=0,最后判断的结果看的是i是0还是非零
如果i是非零,那么就执行第一个分支语句(printf(“a=%d\r\n”,i-10))
如果i是0,那么就执行第二个分支语句(printf(“a=%d\r\n”,i++))

交换两个变量的值,不使用第三个变量

有两种方式,一种是通过±运算符,还有一种是通过异或运算符

a = a + b;
b = a - b;
a = a - b;
or
a = a^b;   // 只能对int,char..
b = a^b;
a = a^b;
or
a ^= b ^= a;

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

  1. 本质:数组指针是一个指针,它指向一个数组的首地址,而指针数组是一个数组,它的每个元素都是一个指针。
  2. 存储内容:数组指针存储的是一个地址,这个地址指向一个数组,而指针数组则存储了一系列的指针变量。
  3. 操作方式:当对数组指针进行加法操作时,它要跨过的是数组元素的数量,即数组长度;而当对指针数组进行加法操作时,它只会增加指针变量的数量。

数组指针中a+1和&a+1的区别

#include <stdio.h>
int main(void)
{
    int a[5];
    printf("%p\n", a);
    printf("%p\n", a+1);
    printf("%p\n", &a);
    printf("%p\n", &a+1);
}

假设a的地址是0x000040,那么这个程序的运行结果是什么?请分析

首先,这个程序试图打印出数组a和相关表达式的地址。为了理解输出结果,我们需要理解这些表达式的含义:

  1. a: 这是一个指向数组a的首元素的指针。它的地址就是数组a的第一个元素在内存中的地址。假设int类型占4字节,那么a的地址就是0x000040。
  2. a+1: 这是一个指向数组a的第二个元素的指针。它的地址是数组a的第二个元素在内存中的地址,即a的地址加上sizeof(int)(在这里是4字节)。所以,a+1的地址是0x000044。
  3. &a: 这是一个指向整个数组a的指针。在C中,数组名(在这里是a)可以被解释为指向其首元素的指针。所以,&a和a的值是一样的,都是指向数组a的首元素的指针,地址都是0x000040。
  4. &a+1: 这是一个指向数组a之后的下一个内存位置的指针。它的地址是数组a的末尾地址加上1,即&a+1的地址是0x000054(假设一个int的大小是4字节,那么5个int的大小就是20字节,所以&a+1的地址是0x000040 + 20 = 0x000054)。

所以,这个程序的运行结果是:

0x000040  
0x000044  
0x000040  
0x000054

再来一个类似的

main()
{ 
	int a[5]={1,2,3,4,5};   
	int *ptr=(int*)(&a+1);   
	printf("%d,%d",*(a+1),*(ptr-1));
}

(a+1)就是a[1],(ptr-1)就是a[4],执行结果是2,5 &a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int),则ptr实际是&(a[5]),也就是a+5 原因如下: &a是数组指针,其类型为 int ( * )[5]; 而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同 a是长度为5的int数组指针,所以要加5 * sizeof(int) 所以ptr实际是a[5] 但是prt与(&a+1)类型是不一样的(这点很重要) 所以prt-1只会减去sizeof(int*) a,&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,a+1是数组下一元素的地址,即a[1];&a是对象(数组)首地址,&a+1是下一个对象的地址,即a[5].

调用free(ptr)对指针进行释放就可以了吗?

调用 free() 函数并不会自动将指针设为 NULL。当你调用 free() 释放内存后,指向该内存的指针就成了悬空指针(dangling pointer)。指针仍然指向一个内存地址,但这个地址已经被操作系统回收,随时可能被分配给其他部分的程序使用。

如果你试图通过这个悬空指针访问内存,程序可能会崩溃,或者更糟糕的是,可能会读到一些无意义的数据。

为了防止这种情况,释放内存后,最好立即将指针设为 NULL:

int *ptr = (int *)malloc(sizeof(int));  
// ... 使用 ptr ...  
free(ptr);  
ptr = NULL; // 防止悬空指针

这样,如果你试图再次访问 ptr,程序会因为访问 NULL 指针而崩溃,这比潜在的悬空指针问题更容易被发现和修复。

需要注意的是,只有指向动态分配(如通过 malloc()、calloc() 或 realloc() 分配)的内存的指针才需要手动释放。指向静态或自动存储期的变量的指针在变量超出其作用域时会自动被清理。对这些指针调用 free() 是错误的。

C语言预编译三大功能

宏定义、文件包含、条件编译。

  1. 宏定义是C语言提供的三种预处理功能的其中一种。宏定义和操作符的区别是:宏定义是替换,不做计算,也不做表达式求解。
  2. 在C语言中文件包含是指一个源文件可以将另一个源文件的全部内容包含进来。该命令的作用是在预编译时,将指定源文件的内容复制到当前文件中。
  3. 条件编译指令将决定哪些代码被编译,而哪些是不被编译的。可以根据 表达式 的值或者某个特定的宏是否被定义来确定编译条件。#if、#else、#elif和#endif指令。

不同数据类型值域范围

看这段代码,并说一下运行结果?

#define Max_CB 500
void LmiQueryCSmd(StructMSgCB * pmsg)
{
	unsigned char ucNum;
	for(ucNum=0;ucNum<Max_CB;ucNum++)
	{
	}                                          
}

结果是死循环
因为unsigned char 无符号字符型表示范围0~255
char 有符号字符型 表示范围-128~127

一句话判断一个数是否是2的若干次幂

printf("%c\r\n",num & (num-1) ? 'n' : 'y');

动态库和静态库有什么区别

动态库(.dll)Dynamic Library
静态库(.lib)Static Library
二者都是为了代码重用

在C语言中,库(Library)是一种包含了一系列函数或变量的集合,它们被封装为一个文件,可以被其他程序复用。库分为两种类型:静态库(Static Library)和动态库(Dynamic Library)。

静态库:
静态库是在编译时期被链接到程序中的。静态库的代码会被直接嵌入到程序中,因此程序运行时不再需要静态库。静态库的主要文件扩展名是“.a”。

优点:
  静态库在编译时就已经链接到程序中,因此程序运行时速度快。静态库的代码会被直接嵌入到程序中,所以程序在没有库的环境下也可以运行。
缺点:
  静态库会增加程序的体积,可能导致可执行文件变大。如果有多个程序使用了同一个静态库,那么每个程序都会包含该库的代码,造成代码冗余。

动态库:
  动态库是在程序运行时被加载的。动态库的代码不会被嵌入到程序中,而是在程序运行时动态加载到内存中。动态库的主要文件扩展名是“.so”(Linux)或“.dll”(Windows)。
优点:
动态库可以减少程序的体积,因为动态库的代码不会被直接嵌入到程序中。如果有多个程序使用了同一个动态库,那么这些程序共享同一份库的代码,节省了内存空间。动态库可以方便地进行版本更新,只需要替换动态库文件即可。
缺点:
动态库在程序运行时才被加载,因此相比于静态库,程序启动速度可能较慢。动态库需要被正确安装和配置,否则程序可能无法找到所需的库文件。
联系:
无论是静态库还是动态库,它们都是封装了一组函数或变量的文件,可以被其他程序复用。在C语言中,我们通常使用头文件(Header File)来声明库中的函数和变量,然后在程序中引用这些头文件。这样,我们就可以在程序中调用库中的函数或访问库中的变量了。

*++p 和 *p++有什么区别?

在这里插入图片描述

指针常量和常量指针的区别?怎么理解更加清晰?

字符常量——>不能修改的字符
整形常量——>不能修改的整形
指针常量——>不能修改的指针

整形指针——>指针指向整形
字符指针——>指针指向字符
常量指针——>指针指向一个常量

sizeof()中的表达式会运行吗?

想一下i的值是多少?

#include <stdio.h>
int main()
{
	int i=1;
	sizeof(++i + ++i);
	printf("i=%d",i);
	return 0;
} 

运行结果是 i=1;
因为sizeof 操作符给出其操作数需要占用的空间大小,它是在编译时就可确定的,所以其操作数即使是一个表达式,也不需要在运行时进行计算( ++i + ++ i)是不会执行的,所以i 的值还是3

逗号表达式

这篇博客写的挺全面的,可以看看ta的
我这边自己总结一下:
1.有无括号的区别

x=表达式1,表达式2,表达式3; // 将表达式1的值赋给x
x=(表达式1,表达式2,表达式3); //将表达式3的值赋给x

2.a++是否在逗号表达式的末尾

#include <stdio.h>
int main()
{
	int a=0,b=0;
	
}

后置++和前置++的优先级比较

后置++的优先级大于前置++的优先级。例如a+++b=(a++)+b

常考算法

二分法

int binary_search(int* arr, int key, int n)
 {
    int low=0;
    int mid;
    int high=n-1;
    while(low<=high)
    {
        mid = (low+high)/2;
        if(key < arr[mid])
            high=mid-1;
        else if(key>arr[mid])
            low=mid+1;
        else
            return mid;
    }
    return -1;
 }
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐