memset初始化结构体之内存泄漏

void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
str – 指向要填充的内存块。
c – 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
n – 要被设置为该值的字节数。
声明:在花括号中间,每个结构代表一个作用域,可以防止命名冲突,
struct {
int number;
int *ptr;
char name [NAME_LEN + 1];
}part1,part2;
如果常用到,可以使用类型定义这样声明,
typedef struct {
int number;
int *ptr;
char name [NAME_LEN + 1];
}part_t;
part_t part1,part2;
还有一种是结构标记声明,
struct part{
int number;
int *ptr;
char name [NAME_LEN + 1];
};
struct part part1, part2;
初始化:可以在声明的同时初始化,如
part_t part1 = {22, &item, “Good boy”}; // 这种初始化和数组很相似。
也可以指定初始化:
part_t part2 = {.number=33, .ptr=&item1, .name=“Another boy”};
相关操作:
(1)成员访问,用“.”操作符,如part2.number = 25;
(2)结构体间赋值,如“part2 = part1;”,直接将结构体part1的内容赋值给了part2,但数组中“array1 = array2;”是不允许的。如果要对数组赋值,可以利用结构体的这个特性,将数组放入结构体中,然后对结构体赋值,则包含在结构体中的数组也会跟着被赋值了。
(3)作为函数参数传入或返回类型的结构体数据,可以考虑用指针来实现(当然直接用结构体传值是可以的),但是这样做可以避免结构体包含存储空间较大造成副本拷贝需要的空间占用,另外在文件操作中,打开一个文件进行操作,打开文件函数返回的就是一个结构体指针,对该文件的操作用指针即可,可以避免文件副本的重复拷贝。
实践示例1 :
环境keil(一种嵌入式的IDE)
typedef struct _RIP_MSG
{
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint8_t ciaddr[4];
uint8_t yiaddr[4];
uint8_t siaddr[4];
uint8_t giaddr[4];
uint8_t chaddr[16];
uint8_t sname[64];
uint8_t file[128];
uint8_t OPT[312];
}RIP_MSG;

uint8_t EXTERN_DHCPBUF[1024];
RIP_MSG* pRIPMSG = (RIP_MSG*)EXTERN_DHCPBUF;
上面是定义了一个结构体,一段可用空间,以及让结构体指针指向这段空间的首地址。
当执行:
memset((void *)pRIPMSG, 0, sizeof(RIP_MSG));语句时,偶尔会死机。
原因分析:
a.在不使用microLib库的情况下
在keil的编译环境下,memset是四字节进行清零操作的。汇编代码中关键的一步是:
STM r0!,{r2-r3,r2,lr} 方括号中的四个寄存器的值均为0,如果r0(即代表结构体的初始地址),因为是4字节赋值,如果r0并不是4字节对齐的话,就会直接死机;所以当我们在定义数组的时候,如果指定四字节对齐,就不会死机、

b.在使用microLIb之后
通过查看汇编代码,发现此时对memset的实现是单字节进行的,因此不存在字节对齐问题。故程序能正常运行。

从上面的一点来看,microLib虽然减小了代码大小,但是因为没有字节对齐,一次赋值一个字节肯定比一次赋值四个字节来的慢,即这是一个时间性能转空间的实例;
实践示例2:
使用C/C++编程时,常使用ZeroMemory、memset或 “={0}”来对结构体对象进行初始化或清零。然而这三种方式都有各自的特点,使用时需谨慎,否则容易出现严重错误;
memset
void *memset(void *s,int ch,size_t n); 是由C Run-time Library提供的提供的函数,作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。由于是语言层面提供,所以可跨平台使用。
示例:
char str[] = “almost every programmer should know memset!”;
memset (str,’-’,6);
puts (str);
输出:------ every programmer should know memset!
ZeroMemory
ZeroMemory是美国微软公司的软件开发包SDK中的一个宏。 其作用是用0来填充一块内存区域。定义式如下:
#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
#define ZeroMemory RtlZeroMemory
由此可见:
1 ZeroMemory实际是用memset实现的;
2 ZeroMemory只能用于windows平台;
注意:

ZeroMemory和memset且于清零时,会将结构中所有字节置0,如果结构体中有虚函数或结构体成员中有虚函数,则会将虚函数指针置0,如果后续程序调用虚函数,空指针很可能导致程序崩溃!

因此,有虚函数或成员中有虚函数的结构体初始化,一定要用构造函数来完成。

另外,如果一个类的结构中包含STL模板(Vector、List、Map等等),那么使用ZeroMemory对这个类的对象中进行清零操作也会引起一系列的崩溃问题(指针指向内存错误、迭代器越界访问等)。所以,再次强烈建议:类(class)只使用构造函数进行初始化,不要调用ZeroMemory进行清零操作。
示例:
在这里插入图片描述
={0}
={0}操作是结构体和数组的一种初始化方式,它是将结构体中基本类型变量赋默认值,当结构体中有非基本类型(例如类对象)时,会编译错误,这也是一种保护。

实践示例3:std::string内存泄露问题之分析解决
问题:string对象在一开始用memset初始化为0,会导致内存泄漏;
原因:
原因在于TOWER_INFO结构体的构造函数内调用了
memset(this, 0, sizeof(_TOWER_INFO);使得string内部指针_Bx._Ptrr值为0,_Myres为0,在这种情况下当string对象被赋值为小字符串(字节数包括结束符小于等于16的字符串)时,因新申请的内存在后来得不到释放,所以这块内存被泄露了,
根据string类内存管理算法(ms vc版本)得知这块内存大小总是16个字节.但当被赋值为大字符串(字节数包括结束符大于16的字符串)时,反而没有内存泄露,这是因为新申请的内存在析构或下次赋值时总能被释放.
总结:
不要轻易零初始化string, vector等stl标准容器及具有动态内存管理的类。

发布了21 篇原创文章 · 获赞 16 · 访问量 3769

猜你喜欢

转载自blog.csdn.net/qq_40632341/article/details/103939677