1. 起因
某天,一个同事跟我反馈说在windows上调试公司产品的一个交易核心时出现了使用未初始化的指针导致后台服务崩溃的情况。示例代码如下所示:
struct sample
{
int* ptr_table[][];
//... other members
}; void test()
{
sample* sample_ptr = new sample[];
for (int i = ; i < ; i++)
sample_ptr[].ptr_table[][i] = new int(i); // 实际系统中是根据初始化数据对sample_ptr数组中的对象进行赋值,但不是所有的对象都有初始化数据;
int* int_ptr = sample_ptr[].ptr_table[][];
if (int_ptr != NULL)
{
printf("ptr1 = 0x%x\n", int_ptr);
*int_ptr = ;
} int_ptr = sample_ptr[].ptr_table[][];
if (int_ptr != NULL)
{
printf("ptr2 = 0x%x\n", int_ptr);
*int_ptr = 100; // crashed here!
}
}
使用未初始化的指针是c++的大忌,但是该代码在产品发布2年左右的时间一直没有出现过问题。唯一的区别是发布运行环境是linux,而调试环境是最近才配置好的windows环境。分别在linux下和windows下对代码进行debug,发现linux下新申请的内存被初始化为0,而windows下新申请的内存却并未初始化。
将sample* sample_ptr = new sample[10]这行改为sample* sample_ptr = new sample[10]()后两个系统执行的结果变一样了,都是被初始化的内存。
那么问题来了:
(1) 为什么相同的代码(new sample[10])在两个系统下表现形式不一样呢?是两个系统的内存分配机制的原因还是类库的原因?
(2) new sample[10]和new sample[10]()的区别到底是什么?
2. 研究
在c++中,一般都是以new/delete来申请和释放内存。对于以下几种new的用法,各自的区别是什么呢?
int* p1 = new int;
int* p2 = new int();
int* p3 = new int();
// define class A;
A* p4 = new A;
A* p5 = new A();
A* p6 = new A[];
A* p7 = new A[]();
在平时写代码的时候,对于一块新申请的内存我都会要对它进行初始化后才会去用它,一般是例如p3的直接初始化或者memset,因此对于new A和new A()两种用法的结果还真是不了解之间的区别。
根据《C++ Primer, Fourth Edition》中5.11节[1] The new and delete Expressions中关于new的描述,new A属于Default Initializing of Dynamically Allocated Objects(动态创建对象的默认初始化),而new A()则属于Value Initializing of Dynamically Allocated Objects(动态创建对象的值初始化)。
当为默认初始化操作时,若被创建的对象没有显式定义默认构造函数,则按照2.3.4节[2]Variable Initialization Rules的规则进行初始化:
- 对象为内置类型时,任何在函数体外定义的变量都会被初始化0(全局变量或者静态变量),在函数体内定义的变量都不会进行初始化
- 对象为类类型时,调用对象的默认构造函数
当为值初始化操作时,若被创建的对象没有显式定义默认构造函数,则认为对该对象进行初始化操作。
不同new的用法对应的初始化的逻辑总结如下:
new A | new A() | new A(parameters) | |
A为内置类型 | 无初始化动作 |
进行值初始化 例A为int类型,则初始化为0 |
进行值初始化,A被初始化为parameters |
A为calss/struct |
调用默认构造函数,A中成员是否 初始化依赖于默认构造函数的实现 |
若自定义了默认构造函数,则调用自定义的默认构造函数。 否则调用系统默认构造函数,并对A中的成员进行值初始化 |
调用A的自定义构造函数 |
因此上面代码的执行结果分别为:
- p1指向了一个未被初始化的int空间
- p2指向了一个被初始化的int空间,其值为0
- p3指向了一个被初始化的int空间,其值为1
- p4指向了一个调用了默认构造函数的实例A,除非A的默认构造函数对A的成员进行初始化,否则A的成员全为未初始化变量
- p5指向了一个调用了默认构造函数的实例A,若A自定义了默认构造函数,A成员变量的初始化依赖于自定义的默认构造函数,反之A的成员变量全为初始化后的变量
- p6、p7指向了调用了默认构造函数的实例A的数组,其执行结果同p4、p5
回到之前的问题,结构体sample是没有自定义默认构造函数的,按照c++标准,new sample的执行结果是sample中的ptr_table是不会被初始化的。这个在windows上是一致的,可是在linux上为什么被初始化成了0了呢?在*上找到了一个类似的问题[3],其答案为:“Memory coming from the OS will be zeroed for security reasons....the C standard says nothing about this. This is strictly an OS behavior. So this zeroing may or may not be present on systems where security is not a concern”。操作系统的安全机制会在用户程序申请内存时对分配的内存进行初始化,以防止别有用心的人从新申请到的内存中读取到敏感数据,例如密码等,正是由于这个linux的特性使程序在首次申请内存时总是能得到一块被初始化的内存。
关于这个安全机制我在网上并没有搜到比较官方的说明文档,也许是我的搜索方式有误或者没有找对关键词,而且发现在程序运行过程中申请的内存也总并不是被初始化了的内存,例如下面的代码:
int size = ;
{
int * p1 = new int[size];
for (int i = ; i < size; i++)
{
p1[i] = i + ;
printf("%d\t", p1[i]);
}
printf("\n");
delete []p1;
} {
int * p1 = new int[size];
for (int i = ; i < size; i++)
printf("%d\t", p1[i]);
delete []p1;
}
printf("\n");
执行结果:
希望对这方面有了解的大神能提供一下相关的资料。
3. 总结
(1) 项目中的代码是明显不符合c++代码规范的,在逻辑上会存在使用未经初始化的指针的现象。个人认为变量的初始化不应依赖于编译器或者系统的实现,而是尽量遵照c++标准或者手工初始化。
(2) 针对class/struct类型,如果没有自定义默认构造函数,不同的new的用法会产生不同的结果,这个在以后写代码的时候要注意。
1:http://shouce.jb51.net/c++/0201721481/ch05lev1sec11.html
2. http://shouce.jb51.net/c++/0201721481/ch02lev1sec3.html#ch02lev2sec13
3:http://*.com/questions/8029584/why-does-malloc-initialize-the-values-to-0-in-gcc
本文为原创内容,若有错误的地方烦请指正
本文地址:http://www.cnblogs.com/morebread/p/4936441.html
由一次程序崩溃引起的对new表达式的再次学习的更多相关文章
-
iOS-----App闪退,程序崩溃---解决方案
1.iOS-中app启动闪退的原因 2.iOS开发-闪退问题-解决之前上架的 App 在 iOS 9 会闪退问题 3.iOS-应用闪退总结 4.iOS开发-捕获程序崩溃日志 5.iOS开发-应用崩溃日 ...
-
iOS - 捕获应用程序崩溃日志
作为一名iOS移动应用开发者,为了确保你的应用程序正确无误,在将应用程序提交到应用商店之前,你必定会进行大量的测试工作:而且在你测试的过程中应用程序运行的很好,但是在应用商店上线之后,还是有用户抱怨应 ...
-
iOS开发-捕获程序崩溃日志
iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时 ...
-
android 连接蓝牙扫码枪,程序崩溃之onConfigurationChanged
当android手机通过蓝牙连接扫码枪时,程序崩溃的原因之一是:键盘弹出或隐藏,触发程序走了onDestory->onCreate的生命周期,从而可能使得页面的某些初始化数据被清除了. 解决方法 ...
-
Android 程序崩溃后的处理
在应用发布以后,由于安卓机型的千差万别 ,可能会出现各种各样的问题,这时候如果我们可以将这些信息收集起来,并进行修改就很不错了.下面就来讨论一下怎么处理程序崩溃以后,错误信息的手机. Java中已经提 ...
-
iOS 中捕获程序崩溃日志
iOS 中捕获程序崩溃日志 (2014-04-22 17:35:59) 转载▼ iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软件都选择的方法.下 ...
-
让linux中的程序崩溃时生成core文件
当我们的linux程序崩溃的时候,常常会有这样的提示: Segmentation fault (core dumped) 段错误 (核心已转储) 提示说生成了core文件,但是此功能 ...
-
水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃
之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock).自己也吃过这个苦头,详见等到花儿也谢了的await. 昨天一个偶然的情况,造成 ...
-
setSupportActionBar(toolbar)导致程序崩溃闪退
最近在做一个项目,使用了第三方的开源项目,主要是想实现android5.0之后推出的MaterialDesign的风格,但是代码已经写好了,发现一运行就闪退,所以就开始debug,发现问题出现在 To ...
随机推荐
-
C# 开发windows服务的一些心得
最近在做一个windows服务的项目,发现并解决了一些问题,拿出来和大家分享一下,以下windows服务简称“服务” 文章会在适合时间更新,因为朋友们在不断提出新的意见或思路,感谢-.- 1.服务如何 ...
-
洛谷P1991无线通讯网[kruskal | 二分答案 并查集]
题目描述 国防部计划用无线网络连接若干个边防哨所.2 种不同的通讯技术用来搭建无线网络: 每个边防哨所都要配备无线电收发器:有一些哨所还可以增配卫星电话. 任意两个配备了一条卫星电话线路的哨所(两边都 ...
-
css secrets----multiple borders
原始文档: https://www.zybuluo.com/freeethy/note/193574 box-shadow solution 只能实现solid border box-shadow表现 ...
-
C#的匿名函数
using System; using System.Collections; using System.Collections.Generic; using System.IO; using Sys ...
-
N个数全排列的非递归算法
//N个数全排列的非递归算法 #include"stdio.h" void swap(int &a, int &b) { int temp; temp = a; a ...
-
MongoDB 学习笔记(四)C# 操作MongoDB
C#驱动对mongodb的操作,目前驱动有两种:官方驱动和samus驱动,不过我个人还是喜欢后者, 因为提供了丰富的linq操作,相当方便. 官方驱动:https://github.com/mongo ...
-
js的引用顺序
注意:Bootstrap中的JS插件依赖于JQuery,因此JQuery要在Bootstrap之前引用!!! 把JS文件引用放入body的最下面,是为了使js在网页全部加载完后才起作用,比如你的js里 ...
-
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. (关于jdbc)
The last packet sent successfully to the server was milliseconds ago. The driver has not received an ...
-
[Leetcode][Python]21: Merge Two Sorted Lists
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 21: Merge Two Sorted Listshttps://oj.le ...
-
【2019雅礼集训】【最大费用流】【模型转换】D2T3 sum
目录 题意 输入格式 输出格式 思路 代码 题意 现在你有一个集合{1,2,3,...,n},要求你从中取出一些元素,使得这些元素两两互质.问你能够取出的元素总和最多是多少? 输入格式 一个整数n 输 ...