华为CC++语言编程规范--笔记
华为C&C++语言编程规范
- 1.基本要求
- 1.1 变量
- 规则1.1.1:指针变量、表示资源描述符的变量、BOOL变量声明必须赋予初值
- 规则1.1.2:指向资源句柄或描述符的变量,在资源释放后立即赋予新值
- 规则1.1.3:类的成员变量必须在构造函数中赋予初值
- 规则1.1.4:严禁对指针变量进行sizeof操作
- 建议1.1.1:尽量使用const
- 建议1.1.2:全局变量的访问如果涉及多个线程,需要考虑多线程竞争条件问题
- 建议1.1.3:同一个函数内,局部变量所占用的空间不要过大
- 1.2 断言(ASSERT)
- 规则1.2.1:断言必须使用宏定义,禁止直接调用系统提供的assert()
- 规则1.2.2:运行时可能会导致的错误,严禁使用断言
- 建议1.2.1:不要将多条语句放在同一个断言中
- 1.3 函数
- 规则1.3.1:数组作为函数参数时,必须同时将其长度作为函数的参数
- 规则1.3.3:不对内容进行修改的指针型参数,定义为const
- 建议1.3.2:字符串或指针作为函数参数时,请检查参数是否为NULL
- 1.4 异常机制
- 规则1.4.1:禁用C++异常机制
- 1.5 类
- 规则1.5.1:构造函数内不能做任何有可能失败的操作
- 规则1.5.2:严禁在构造函数中创建线程
- 规则1.5.3:如果类的公共接口中返回类的私有数据地址,则必须加const类型
- 建议1.5.4:尽量避免定义public成员
- 1.6 安全退出
- 规则1.6.1:禁用atexit函数
- 规则1.6.2:严禁调用kill、TerminateProcess函数终止其他进程
- 规则1.6.3:禁用pthread_exit、ExitThread函数
- 建议1.6.1:禁用exit、ExitProcess函数(main函数除外)
- 建议1.6.2:禁用abort函数
- 2.字符串/数组操作
- 规则2.1:确保有足够的存储空间
- 规则2.2:对字符串进行存储操作,确保字符串有’\0’结束符
- 规则2.3:外部数据作为数组索引时必须确保在数组大小范围内
- 规则2.4:外部输入作为内存操作相关函数的复制长度时,需要校验其合法性
- 规则2.5:调用格式化函数时,禁止format参数由外部可控
- 3.整数
- 规则3.1:整数之间运算时必须严格检查,确保不会出现溢出、反转、除0
- 规则3.2:整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值
- 规则3.3:禁止对有符号整数进行位操作符运算
- 规则3.4:禁止整数与指针间的互相转化
- 规则3.5:禁止对指针进行逻辑或位运算(&&、||、!、~、>>、<<、&、^、|)
- 规则3.6:循环次数如果受外部数据控制,需要校验其合法性
- 4.内存
- 规则4.1:内存申请前,必须对申请内存大小进行合法性校验
- 规则4.2:内存分配后必须判断是否成功
- 规则4.3:禁止引用未初始化的内存
- 规则4.3:禁止引用未初始化的内存
- 规则4.4:内存释放之后立即赋予新值
- 规则4.5:禁止使用realloc()函数
- 5.文件输入/输出
- 规则5.1:创建文件时必须显式指定合适的文件访问权限
1.基本要求
1.1 变量
规则1.1.1:指针变量、表示资源描述符的变量、BOOL变量声明必须赋予初值
变量声明赋予初值,可以避免由于编程人员的疏忽导致的变量未初始化引用。
示例:
SOCKET s = INVALID_SOCKET;
unsigned char *msg = NULL;
BOOL success = FALSE;
int fd = -1;
以下代码,由于变量声明未赋予初值,在最后free的时候出错。
char *message; // 错误!必须声明为 char *message = NULL;...
if (condition) {message = (char *)malloc(len);...}...
if (message != NULL) {free(message); //如果condition未满足,会造成free未初始化的内存。
}
规则1.1.2:指向资源句柄或描述符的变量,在资源释放后立即赋予新值
资源释放后,对应的变量应该立即赋予新值,防止后续又被重新引用。如果释放语句刚好在变量作用域的最后一句,可以不进行赋值。
示例:
SOCKET s = INVALID_SOCKET;
unsigned char *msg = NULL;
int fd = -1;...closesocket(s);s = INVALID_SOCKET;...free(msg);msg = (unsigned char *)malloc(...); //msg变量又被赋予新值...close(fd);fd = -1;...
规则1.1.3:类的成员变量必须在构造函数中赋予初值
变量声明赋予初值,可以避免由于编程人员的疏忽导致的变量未初始化引用。
示例:
class CMsg {
public:CMsg();~CMsg();protected:int size;unsigned char *msg;
};CMsg::CMsg()
{size = 0;msg = NULL;}
规则1.1.4:严禁对指针变量进行sizeof操作
编码人员往往由于粗心,将指针当做数组进行sizeof操作,导致实际的执行结果与预期不符。 下面的代码,buffer和path分别是指针和数组,编码人员想对这2个内存进行清0操作,但由于编码人员的疏忽,第5行代码,将内存大小误写成了sizeof,与预期不符。
char *buffer = (char *)malloc(size);
char path[MAX_PATH] = {0};
...
memset(path, 0, sizeof(path));
memset(buffer, 0, sizeof(buffer));
如果要判断当前的指针类型大小,请使用sizeof(char *)的方式。
建议1.1.1:尽量使用const
在变量声明前加const关键字,表示该变量不可被修改,这样就可以利用编译器进行类型检查,将代码的权限降到更低。
例如下面是不好的定义:
float pi = 3.14159f;
应当这样定义:
const float PI = 3.14159f;
建议1.1.2:全局变量的访问如果涉及多个线程,需要考虑多线程竞争条件问题
应该尽可能减少全局变量的使用,如果多个线程会访问到该全局变量,则访问过程必须加锁。 以下代码中,g_list是全局变量,对链表进行搜索操作时,在while循环语句的前后加锁。
ItemList *g_list = NULL;
ItemList *SearchList(const char *name)
{Lock();ItemList *p = g_list;while (p != NULL){if (strcmp(p->name, name) == 0){break;}p = p->next;}UnLock();return p;
}
性能敏感的代码,请考虑采用原子操作或者无锁算法。
建议1.1.3:同一个函数内,局部变量所占用的空间不要过大
程序在运行期间,函数内的局部变量保存在栈中,栈的大小是有限的。如果申请过大的静态数组,可能导致出现运行出错。 建议在申请静态数组的时候,大小不超过0x1000。 下面的代码,buff申请过大,导致栈空间不够,程序发生stackoverflow异常。
#define MAX_BUFF 0x1000000
int Foo()
{char buff[MAX_BUFF] = {0};...
}
1.2 断言(ASSERT)
断言是一种除错机制,用于验证代码是否符合编码人员的预期。编码人员在开发期间应该对函数的参数、代码中间执行结果合理地使用断言机制,确保程序的缺陷尽量在测试阶段被发现。 断言被触发后,说明程序出现了不应该出现的严重错误,程序会立即提示错误,并终止执行。 断言必须用宏进行定义,只在调试版本有效,最终发布版本不允许出现assert函数,例如:
#include <assert.h>
#ifdef DEBUG
#define ASSERT(f) assert(f)
#else
#define ASSERT(f) ((void)0)
#endif
下面的函数VerifyUser,上层调用者会保证传进来的参数是合法的字符串,不可能出现传递非法参数的情况。因
此,在该函数的开头,加上4个ASSERT进行校验。
BOOL VerifyUser(const char *userName, const char *password)
{ASSERT(userName != NULL);ASSERT(strlen(userName) > 0);ASSERT(password != NULL);ASSERT(strlen(password) > 0);...
}
以下的switch,由于不可能出现default的情况,所以在default处直接调用ASSERT:
enum {COLOR_RED = 1,COLOR_GREEN,COLOR_BLUE
};
...
switch (color) {case COLOR_RED:...case COLOR_GREEN:...case COLOR_BLUE:...default: {ASSERT(0);}
}
以下代码,SendMsg是CMsg类的成员函数,socketID是成员变量,在调用SendMsg的时候必须保证socketID已经
被初始化,因此在此处用ASSERT判断socketID的合法性。
CMsg::CMsg()
{socketID = INVALID_SOCKET;
}
int CMsg::SendMsg(const char *msg, int len)
{ASSERT(socketID != INVALID_SOCKET);...ret = send(socketID, msg, len, 0);...
}
在linux内核中定义ASSERT宏,可以采用如下方式:
#ifdef DEBUG
#define ASSERT(f) BUG_ON(!(f))
#else
#define ASSERT(f) ((void)0)
#endif
规则1.2.1:断言必须使用宏定义,禁止直接调用系统提供的assert()
断言只能在调试版使用,断言被触发后,程序会立即退出,因此严禁在正式发布版本使用断言,请通过编译选项进行控制。 错误用法如:
int Foo(int *array, int size)
{assert(array != NULL);...
}
规则1.2.2:运行时可能会导致的错误,严禁使用断言
断言不能用于校验程序在运行期间可能导致的错误。 以下代码的所有ASSERT的用法是错误的。
FILE *fp = fopen(path, "r");
ASSERT(fp != NULL); //文件有可能打开失败
char *str = (char *)malloc(MAX_LINE
ASSERT(str != NULL); //内存有可能分配失败
ReadLine(fp, str);
char *p = strstr(str, 'age=');
ASSERT(p != NULL); //文件中不一定存在该字符串
int age = atoi(p+4);
ASSERT(age > 0); //文件内容不一定符合预期
建议1.2.1:不要将多条语句放在同一个断言中
为了更加准确地发现错误的位置,每一条断言只校验一个条件。 下面的断言同时校验多个条件,在断言触发的时候,无法判断到底是哪一个条件导致的错误:
int Foo(int *array, int size)
{ASSERT(array != NULL && size > 0 && size < MAX_SIZE);...
}
应该将每个条件分开:
int Foo(int *array, int size)
{ASSERT(array != NULL);ASSERT(size > 0);ASSERT(size < MAX_SIZE);...
}
1.3 函数
规则1.3.1:数组作为函数参数时,必须同时将其长度作为函数的参数
通过函数参数传递数组或一块内存进行写操作时,函数参数必须同时传递数组元素个数或所传递的内存块大小,否则函数在使用数组下标或访问内存偏移时,无法判断下标或偏移的合法范围,产生越界访问的漏洞。 以下代码中,函数ParseMsg不知道msg的范围,容易产生内存越界访问漏洞。
int ParseMsg(BYTE *msg)
{...
}
...
size_t len = ...
BYTE *msg = (BYTE *)malloc(len); //此处分配的内存块等同于字节数组
...
ParseMsg(msg);
...
正确的做法是将msg的大小作为参数传递到ParseMsg中,如下代码:
int ParseMsg(BYTE *msg, size_t msgLen)
{ASSERT(msg != NULL);ASSERT(msgLen != 0);...
}
...
size_t len = ...
BYTE *msg = (BYTE *)malloc(len);
...
ParseMsg(msg, len);
...
规则1.3.3:不对内容进行修改的指针型参数,定义为const
如果参数是指针型参数,且内容不会被修改,请定义为const类型。
int Foo(const char *filePath)
{...int fd = open(filePath, ...);...
}
建议1.3.2:字符串或指针作为函数参数时,请检查参数是否为NULL
如果字符串或者指针作为函数参数,为了防止空指针引用错误,在引用前必须确保该参数不为NULL,如果上层调用者已经保证了该参数不可能为NULL,在调用本函数时,在函数开始处可以加ASSERT进行校验。 例如下面的代码,因为BYTE *p有可能为NULL,因此在使用前需要进行判断。
int Foo(int *p, int count)
{if (p != NULL && count > 0){int c = p[0];}...int Foo2(){int *arr = ...int count = ...Foo(arr, count);...}
}
下面的代码,由于p的合法性由调用者保证,对于Foo函数,不可能出现p为NULL的情况,因此加上ASSERT进行校
验。
int Foo(int *p, int count)
{ASSERT(p != NULL); //ASSERT is added to verify p.ASSERT(count > 0);int c = p[0];...
}
int Foo2()
{int *arr = ...int count = ......if (arr != NULL && count > 0){Foo(arr, count);}...
}
1.4 异常机制
规则1.4.1:禁用C++异常机制
严禁使用C++的异常机制,所有的错误都应该通过错误值在函数之间传递并做相应的判断, 而不应该通过异常机制进行错误处理。 编码人员必须完全掌控整个编码过程,建立攻击者思维,增强安全编码意识,主动把握有可能出错的环节。而使用C++异常机制进行错误处理,会削弱编码人员的安全意识。 异常机制会打乱程序的正常执行流程,使程序结构更加复杂,原先申请的资源可能会得不到有效清理。 异常机制导致代码的复用性降低,使用了异常机制的代码,不能直接给不使用异常机制的代码复用。 异常机制在实现上依赖于编译器、操作系统、处理器,使用异常机制,导致程序执行性能降低。 在二进制层面,程序被加载后,异常处理函数增加了程序的被攻击面,攻击者可以通过覆盖异常处理函数地址,达到攻击的效果。 例外: 在接管C++语言本身抛出的异常(例如new失败、STL)、第三方库(例如IDL)抛出的异常时,可以使用异常机制,例如:
int len = ...;
char *p = NULL;
try {p = new char[len];
}
catch (bad_alloc) {...abort();
}
1.5 类
规则1.5.1:构造函数内不能做任何有可能失败的操作
构造函数没有返回值,不能做错误判断,因此在构造函数内,不能做任何有可能失败的操作。 下面的代码中,
open、new、ConnectServer都有可能失败,这些操作不应该放在构造函数内。
CFoo::CFoo()
{int fd = open(...);char *str = new char[...];BOOL b = ConnectServer(...);...
}
规则1.5.2:严禁在构造函数中创建线程
构造函数内仅作成员变量的初始化工作,其他的操作通过成员函数完成。
规则1.5.3:如果类的公共接口中返回类的私有数据地址,则必须加const类型
class CMsg {
public:CMsg();~CMsg();Const unsigned char *GetMsg();
protected:int size;unsigned char *msg;
};CMsg::CMsg()
{size = 0;msg = NULL;
}
const unsigned char *CMsg::GetMsg()
{return msg;
}
建议1.5.4:尽量避免定义public成员
类成员进行定义的时候,需要考虑类的功能,尽量减少对外接口的暴露。
1.6 安全退出
规则1.6.1:禁用atexit函数
atexit函数注册若干个有限的函数,当exit被调用后,自动调用由atexit事先注册的函数。 当资源不再使用后,编码人员应该立即主动地进行清理,而不应该在最终程序退出后通过事先注册的例程被动地清理。
例外: 作为服务维测监控功能,为定位程序异常退出原因的模块,可以作为例外使用atexit()函数。
规则1.6.2:严禁调用kill、TerminateProcess函数终止其他进程
调用kill、TerminateProcess等函数强行终止其他进程(如kill -9),会导致其他进程的资源得不到清理。 对于进程间通信,应该主动发送一个停止命令,通知对方进程安全退出。 当发送给对方进程退出信号后,在等待一定时间内如果对方进程仍然未退出,可以调用kill、TerminateProcess函数。
if (WaitForRemoteProcessExit(...) == TIME_OUT) {kill(...); //目标进程在限定时间内仍然未退出,强行结束目标进程
}
规则1.6.3:禁用pthread_exit、ExitThread函数
严禁在线程内主动终止自身线程,线程函数在执行完毕后会自动、安全地退出。主动终止自身线程的操作,不仅导致代码复用性变差,同时容易导致资源泄漏错误。
建议1.6.1:禁用exit、ExitProcess函数(main函数除外)
程序应该安全退出,除了main函数以外,禁止任何地方调用exit、ExitProcess函数退出进程。直接退出进程会导致代码的复用性降低,资源得不到有效地清理。 程序应该通过错误值传递的机制进行错误处理。 以下代码加载文件,加载过程中如果出错,直接调用exit退出:
void LoadFile(const char *filePath)
{FILE* fp = fopen(filePath, "rt");if (fp == NULL) {exit(0);}...
}
正确的做法应该通过错误值传递机制,例如:
BOOL LoadFile(const char *filePath)
{BOOL ret = FALSE;FILE* fp = fopen(filePath, "rt");if (fp != NULL) {...}...return ret;
}
建议1.6.2:禁用abort函数
abort会导致程序立即退出,资源得不到清理。 例外: 只有发生致命错误,程序无法继续执行的时候,在错误处理函数中使用abort退出程序,例如:
void FatalError(int sig)
{abort();
}
int main(int argc, char *argv[])
{signal(SIGSEGV, FatalError);...
}
2.字符串/数组操作
规则2.1:确保有足够的存储空间
部分字符串处理函数由于设计时安全考虑不足,或者存在一些隐含的目的缓冲区长度要求,容易被误用,导致缓冲区写溢出。典型函数如itoa,realpath。 以下的代码,试图将数字转为字符串,但是目标存储空间的长度不足。
int num = ...
char str[8] = {0};
itoa(num, str, 10); // 10进制整数的最大存储长度是12个字节
以下的代码,试图将路径标准化,但是目标存储空间的长度不足。
char resolvedPath[100] = {0};
realpath(path, resolvedPath); //realpath函数的存储缓冲区长度是由PATH_MAX常量定义,或是由
_PC_PATH_MAX系统值配置的,通常都大于100字节以下的代码,在对外部数据进行解析并将内容保存到name中,考虑了name的大小,是正确的做法。
char *msg = GetMsg();
...
char name[MAX_NAME] = {0};
int i=0;
//必须考虑msg不包含预期的字符’\n’
while (*msg != '\0' && *msg != '\n' && i < sizeof(name) - 1) {name[i++] = *msg++;
}
name[i] = '\0'; //保证最后有’\0’
规则2.2:对字符串进行存储操作,确保字符串有’\0’结束符
对字符串进行存储操作,必须确保字符串有’\0’结束符,否则在后续的调用strlen等操作中,可能会导致内存越界访问漏洞。
规则2.3:外部数据作为数组索引时必须确保在数组大小范围内
外部数据作为数组索引对内存进行访问时,必须对数据的大小进行严格的校验,否则为导致严重的错误。 下面的代码,通过if语句判断offset的合法性:
int Foo(BYTE *buffer, int size)
{...int offset = ReadIntFromMsg();if (offset >= 0 && offset < size) {BYTE c = buffer[offset];...}...
}
规则2.4:外部输入作为内存操作相关函数的复制长度时,需要校验其合法性
在调用内存操作相关的函数时(例如memcpy、memmove、memcpy_s、memmove_s等),如果复制长度外部可控,则必须校验其合法性,否则容易导致内存溢出。 下例中,循环长度来自设备外部报文,由于没有校验大小,可造成缓冲区溢出:
typedef struct BigIntType {unsigned int length;char val[MAX_INT_DIGITS];
}BigInt;BigInt *AsnOctsToBigInt(const AsnOcts *asnOcts)
{BigInt *bigNumber = NULL;...for (i = 0; i < asnOcts->octetLen; i++) {bigNumber->val[i] = asnOcts->octs[i];}...
}
规则2.5:调用格式化函数时,禁止format参数由外部可控
调用格式化函数时,如果format参数由外部可控,会造成字符串格式化漏洞。 这些格式化函数有:
char *msg = GetMsg();
...
printf(msg);
推荐做法:
char *msg = GetMsg();
...
printf("%s\n", msg);
3.整数
规则3.1:整数之间运算时必须严格检查,确保不会出现溢出、反转、除0
在计算机中,整数存储的长度是固定的(例如32位或64位),当整数之间进行运算时,可能会超过这个最大固定长度,导致整数溢出或反转,使得实际计算结果与预期结果不符。 如果涉及到除法或者求余操作,必须确保除数不为0。
错误示例1:
size_t width = ReadByte();
size_t height = ReadByte();
size_t total = width * height; //可能整数溢出
void *bitmaps = malloc(total);
推荐做法1:
size_t width = ReadByte();
size_t height = ReadByte();
if (width == 0 || height == 0 || width > MAX_WIDTH || height > MAX_HEIGHT) {
//error...
}
size_t total = width * height; // MAX_WIDTH * MAX_HEIGHT 不会溢出
void *bitmaps = malloc(total);
错误示例2:
size_t a = ReadByte();
size_t b = 1000 / a; //a可能是0
size_t c = 1000 % a; //a可能是0
...
推荐做法2:
size_t a = ReadByte();
if (a == 0) {//error...
}
size_t b = 1000 / a; //a不可能是0
size_t c = 1000 % a; //a不可能是0
...
规则3.2:整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值
由于整数在运算过程中可能溢出,当运算结果赋值给比他更大类型,或者和比他更大类型进行比较时,会导致实际结果与预期结果不符。 请观察以下二个代码及其输出:
int main(int argc, char *argv[])
{unsigned int a = 0x10000000;unsigned long long b = a * 0xab;printf("b = %llX\n", b);return 0;
}// 输出: b = B0000000
规则3.3:禁止对有符号整数进行位操作符运算
位操作符(~、>>、<<、&、^、|)应该只用于无符号整型操作数。 错误示例:
int data = ReadByte();
int a = data >> 24;
推荐做法:(为简化示例代码,此处假设ReadByte函数实际不存在返回值小于0的情况)
unsigned int data = (unsigned int)ReadByte();
unsigned int a = data >> 24;
规则3.4:禁止整数与指针间的互相转化
指针的大小随着平台的不同而不同,强行进行整数与指针间的互相转化,降低了程序的兼容性,在转换过程中可能引起指针高位信息的丢失。
错误示例:
char *ptr = ...;
unsigned int number = (unsigned int)ptr;
推荐做法:
char *ptr = ...;
uintptr_t number = (uintptr_t)ptr;
规则3.5:禁止对指针进行逻辑或位运算(&&、||、!、~、>>、<<、&、^、|)
对指针进行逻辑运算,会导致指针的性质改变,可能产生内存非法访问的问题。 下面是错误的用法:
BOOL dealName(const char *nameA, const char *nameB)
{...if (nameA)...if (!nameB)...
}
下面是正确的用法:
BOOL dealName(const char *nameA, const char *nameB)
{...if (nameA != NULL)...if (nameB == NULL)...
}
例外: 为检查地址对齐而对地址指针进行的位运算可以作为例外。
规则3.6:循环次数如果受外部数据控制,需要校验其合法性
如下示例中,由于循环条件受外部输入的报文内容控制,可进入死循环:
unsigned char *FindAttr(unsigned char type, unsigned char *msg, size_t inputMsgLen)
{...msgLength = ntohs(*(unsigned short *)&msg[RD_LEA_PKT_LENGTH]);...while (msgLength != 0) {attrType = msg[0];attrLength = msg[RD_LEA_PKT_LENGTH];...msgLength -= attrLength;msg += attrLength;}...
}
此例中,需要检查报文的实际可读长度,报文内容提供的循环增量(避免为0),以防止缓冲区溢出。
4.内存
规则4.1:内存申请前,必须对申请内存大小进行合法性校验
内存申请的大小可能来自于外部数据,必须检查其合法性,防止过多地、非法地申请内存。不能申请0长度的内
存。 例如:
int Foo(int size)
{if (size <= 0) {
//error...}...char *msg = (char *)malloc(size);...
}
规则4.2:内存分配后必须判断是否成功
char *msg = (char *)malloc(size);
if (msg != NULL) {...
}
规则4.3:禁止引用未初始化的内存
malloc、new分配出来的内存没有被初始化为0,要确保内存被引用前是被初始化的。 以下代码使用malloc申请内
存,在使用前没有初始化:
int *CalcMetrixColomn( int **metrix ,int *param, size_t size )
{int *result = NULL;...size_t bufSize = size * sizeof(int);...result = (int *)malloc(bufSize);...result[0] += metrix[0][0] * param[0];...return result;
}
规则4.3:禁止引用未初始化的内存
以下代码使用memset_s()对分配出来的内存清零。
int *CalcMetrixColomn(int **metrix ,int *param, size_t size)
{int *result = NULL;...size_t bufSize = size * sizeof(int);...result = (int *)malloc(bufSize);...int ret = memset_s(result, bufSize, 0, bufSize); //【修改】确保内存被初始化后才被引...result[0] += metrix[0][0] * param[0];...return result;
}
规则4.4:内存释放之后立即赋予新值
悬挂指针可能会导致双重释放(double-free)以及访问已释放内存的危险。消除悬挂指针以及消除众多与内存相
关危险的一个最为有效地方法就是当指针使用完后将其置新值。 如果一个指针释放后能够马上离开作用域,因为它
已经不能被再次访问,因此可以无需对其赋予新值。
示例:
char *message = NULL;
...
message = (char *)malloc(len);
...
if (...) {free(message); //在这个分支内对内存进行了释放message = NULL; //释放后将指针赋值为NULL
}
...
if (message != NULL) {free(message);message = NULL;
}
规则4.5:禁止使用realloc()函数
realloc()原型如下:
void *realloc(void *ptr, size_t size);
随着参数的不同,其行为也是不同。
1) 当ptr不为NULL,且size不为0时,该函数会重新调整内存大小,并将新的内存指针返回,并保证最小的size的内容不变;
2) 参数ptr为NULL,但size不为0,那么行为等同于malloc(size);
3) 参数size为0,则realloc的行为等同于free(ptr)。 由此可见,一个简单的C函数,却被赋予了3种行为,这不是一个设计良好的函数。虽然在编码中提供了一些便利性,但是却极易引发各种bug。
5.文件输入/输出
规则5.1:创建文件时必须显式指定合适的文件访问权限
创建文件时,如果不显式指定合适访问权限,可能会让未经授权的用户访问该文件。 下列代码没有显式配置文件的访问权限。
int fd = open(fileName, O_CREAT | O_WRONLY); //【错误】缺少访问权限设置
推荐做法:
int fd = open(fileName, O_CREAT | O_WRONLY, S_IRUSR|S_IWUSR);
相关文章:
华为CC++语言编程规范--笔记
华为C&C语言编程规范 1.基本要求1.1 变量规则1.1.1:指针变量、表示资源描述符的变量、BOOL变量声明必须赋予初值规则1.1.2:指向资源句柄或描述符的变量,在资源释放后立即赋予新值规则1.1.3:类的成员变量必须在构造函数中赋予初…...

洛谷_P5461 赦免战俘_python写法
捋一下这道题的思路,理解了题目的意思之后我们知道这道题一定会用递归。 那递归的出口很简单,矩阵为1x1的时候就是题目所说的不能再细分下去的意思。 问题就在于递归体。 我对于递归体的理解是找到一个普适的规律,这个规律适用于每一次的递归…...

RabbitMQ的延迟队列实现[死信队列](笔记二)
上一篇已经讲述了实现死信队列的rabbitMQ服务配置,可以点击: RabbitMQ的延迟队列实现(笔记一) 目录 搭建一个新的springboot项目模仿订单延迟支付过期操作启动项目进行测试 搭建一个新的springboot项目 1.相关核心依赖如下 <dependency><groupId>org.…...
买电脑注意事项之CPU型号后面的字母都代表什么意思
在 CPU 型号后面的字母通常表示该 CPU 的一些特性或用途。不同的字母可能代表不同的系列、性能级别、功耗特性等。以下是一些常见的 CPU 后缀字母及其可能的含义: U(例如:i5-8250U): Ultra Low Power:表示低功耗&#…...
Transformer实战-系列教程11:SwinTransformer 源码解读4(WindowAttention类)
🚩🚩🚩Transformer实战-系列教程总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 点我下载源码 SwinTransformer 算法原理 SwinTransformer 源码解读1(项目配置/SwinTr…...

Jenkins(本地Windows上搭建)上传 Pipeline构建前端项目并将生成dist文件夹上传至指定服务器
下载安装jdk https://www.oracle.com/cn/java/technologies/downloads/#jdk21-windows 下载jenkins window版 双击安装 https://www.jenkins.io/download/thank-you-downloading-windows-installer-stable/ 网页输入 http://localhost:8088/ 输入密码、设置账号、安装推…...
Elasticsearch 安装和配置脚本文档
Elasticsearch 安装和配置脚本文档 目录 **Elasticsearch 安装和配置脚本文档**0.**概述**1.**使用方法:**2.**脚本步骤:**3. **完整代码如下:** 0.概述 此Bash脚本用于自动化在CentOS 7系统上安装和配置Elasticsearch(ES&#x…...
【Android辟邪】之:gradle——在项目间共享依赖关系版本
翻译和简单修改自:https://docs.gradle.org/current/userguide/platforms.html#sec:sharing-catalogs 建议看原文(有能力的话) 现在 Gradle 脚本可以使用两种语法编写:Kotlin 和 Groovy 本文只使用kotlin脚本语法,更…...
Qt 项目树工程,拷贝子项目dll到子项目exe运行路径
1、项目树工程 2、项目树列表 ---- BuildAll -------- App (exe) -------- Database (dll) 注:使用 子项目–>添加库–>内部库 的方式 3、qmake 内置的变量 $$OUT_PWD 表示输出文件(如可执行文件…...
进程间通信方式
1>内核提供的原始通信方式有三种 1)无名管道 2)有名管道 3)信号 2>System V提供了三种通信方式 4)消息队列 5)共享内存 6)信号量(信号灯集) 3>套接字通信 7)socke…...

[linux]:匿名管道和命名管道(什么是管道,怎么创建管道(函数),匿名管道和命名管道的区别,代码例子)
目录 一、匿名管道 1.什么是管道?什么是匿名管道? 2.怎么创建匿名管道(函数) 3.匿名管道的4种情况 4.匿名管道有5种特性 5.怎么使用匿名管道?匿名管道有什么用?(例子) 二、命名…...

Python调用matlab程序
matlab官网:https://ww2.mathworks.cn/?s_tidgn_logo matlab外部语言和库接口,包括 Python、Java、C、C、.NET 和 Web 服务。 matlab和python的版本 安装依赖配置 安装matlab的engine 找到matlab的安装目录:“xxx\ extern\engines\python…...

FlinkSql 窗口函数
Windowing TVF 以前用的是Grouped Window Functions(分组窗口函数),但是分组窗口函数只支持窗口聚合 现在FlinkSql统一都是用的是Windowing TVFs(窗口表值函数),Windowing TVFs更符合 SQL 标准且更加强大…...

十分钟GIS——geoserver+postgis+udig从零开始发布地图服务
1数据库部署 1.1PostgreSql安装 下载到安装文件后(postgresql-9.2.19-1-windows-x64.exe),双击安装。 指定安装目录,如下图所示 指定数据库文件存放目录位置,如下图所示 指定数据库访问管理员密码,如下图所…...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Span组件
鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Span组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Span组件 鸿蒙(HarmonyOS)作为Text组件的子组件࿰…...

Leetcode—42. 接雨水【困难】
2024每日刷题(112) Leetcode—42. 接雨水 空间复杂度为O(n)的算法思想 实现代码 class Solution { public:int trap(vector<int>& height) {int ans 0;int n height.size();vector<int> l(n);vector<int> r(n);for(int i 0; …...

[Python] opencv - 什么是直方图?如何绘制图像的直方图?如何对直方图进行均匀化处理?
什么是直方图? 直方图是一种统计图,用于展示数据的分布情况。它将数据按照一定的区间或者组进行划分,然后计算在每个区间或组内的数据频数或频率(即数据出现的次数或占比),然后用矩形或者柱形图的形式将这…...
ppi rust开发 python调用
创建python的一个测试工程 python -m venv venv .\venv\Scripts\activatepip install cffi创建一个rust的lib项目 cargo new --lib pyrustlib.rs #[no_mangle] pub extern "C" fn rust_add(x: i32, y: i32) -> i32 {x y }Cargo.toml [package] name "p…...
网站后端开发 thinkphp6 入门教程合集(更新中)
thinkphp6 入门(1)--安装、路由规则、多应用模式 thinkphp6 入门(1)--安装、路由规则、多应用模式_软件工程小施同学的博客-CSDN博客 thinkphp6 入门(2)--视图、渲染html页面、赋值 thinkphp6 入门&#x…...

Web前端框架-Vue(初识)
文章目录 web前端三大主流框架**1.Angular****2.React****3.Vue**什么是Vue.js 为什么要学习流行框架框架和库和插件的区别一.简介指令v-cloakv-textv-htmlv-pre**v-once**v-onv-on事件函数中传入参数事件修饰符双向数据绑定v-model 按键修饰符自定义按键修饰符别名v-bind(属性…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...