c++分配内存空间的重复

一、简介

      最近写C++程序时,调试过程中打印了一下QList中使用的指针内存地址,意外发现以前使用的逻辑地址再delete后又被重新使用了,因此总是使用char *address1 = new char;char *address2 = new char;if (address1 != address2) {}来进行指针的比较,如果他们顺序执行没什么问题,地址肯定不同。但如果new address1时,被他人调用char *p = address1,然后delete p;p=NULL,接着new address2就危险了,这时可能address1 == address2了。所以写下文章以给自己一个警告(也主要是指针管理不严,自己new的东西绝对不能让别人delete)。

二、详解

1、测试程序

在qt4环境下运行:

struct Source {
    char *p;
    int num;
};
int mian()
{
    Source *point;
    QList<Source *>list;
    for (int index = 0; index < 5; index++) {
        qDebug() << "------------------";
        for(int i = 0; i < 5; i++) {
            point = new Source;
            list.append(point);
            qDebug() << "point=" << point;
        }
        for(int i = 0; i < 5; i++) {
            delete list[i];
            list[i] = NULL;
        }
        list.clear();
    }
    return 0;
}

2、运行结果


结构体指针分5组(每组先new后delete):发现每组point0-point4肯定逻辑地址不同,但5组中有逻辑地址被重复使用,比如

0x12b5950在每组中都能涉及到。

    Source *point;
    QList<Source *>list;
    point = new Source;
    qDebug() << "point=" << point;
    delete point;
    point = NULL;
    point = new Source;
    qDebug() << "point=" << point;
    delete point;
    point = NULL;
point= 0xe6c210

point= 0xe6c210

运行前后指针地址就也许相同了。

三、总结

(1)经过分析,在C++程序中使用QMap或map将结构体指针作为key值(QMap<Source*,QString>map)的做法还是需要注意的。

(2)特别是每次保留上一次的指针(该指针可能被别人delete直至赋为NULL,但自己没清空),然后new出新指针的,比较判断数据是否为新的数据的做法坚决不用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
首先要理解基本的原理,2台电脑间实现TCP通讯,首先要建立起连接,在这里要提到服务器端与客户端,两个的区别通俗讲就是主动与被动的关系,两个人对话,肯定是先有人先发起会话,要不然谁都不讲,谈什么话题,呵呵!一样,TCPIP下建立连接首先要有一个服务器,它是被动的,它只能等待别人跟它建立连接,自己不会去主动连接,那客户端如何去连接它呢,这里提到2个东西,IP地址和端口号,通俗来讲就是你去拜访某人,知道了他的地址是一号大街2号楼,这个是IP地址,那么1号楼这么多门牌号怎么区分,嗯!门牌号就是端口(这里提到一点,我们访问网页的时候也是IP地址和端口号,IE默认的端口号是80),一个服务器可以接受多个客户端的连接,但是一个客户端只能连接一台服务器,在连接后,服务器自动划 分内存区域以分配各个客户端的通讯,那么,那么多的客户端服务器如何区分,你可能会说,根据IP么,不是很完整,很简单的例子,你一台计算机开3个QQ,服务器怎么区分?所以准确的说是IP和端口号,但是客户端的端口号不是由你自己定的,是由计算机自动分配的,要不然就出现端口冲突了,说的这么多,看下面的这张图就简单明了了。 在上面这张图中,你可以理解为程序A和程序B是2个SOCKET程序,服务器端程序A设置端口为81,已接受到3个客户端的连接,计算机C开了2个程序,分别连接到E和D,而他的端口是计算机自动分配的,连接到E的端口为789,连接到D的为790。 了解了TCPIP通讯的基本结构后,接下来讲解建立的流程,首先声明一下我用的开发环境是Visual Studio2008版的,语言C#,组件System.Net.Sockets,流程的建立包括服务器端的建立和客户端的建立,如图所示: 二、实现: 1.客户端: 第一步,要创建一个客户端对象TcpClient(命名空间在System.Net.Sockets),接着,调用对象下的方法BeginConnect进行尝试连接,入口参数有4个,address(目标IP地址),port(目标端口号),requestCallback(连接成功后的返调函数),state(传递参数,是一个对象,随便什么都行,我建议是将TcpClient自己传递过去),调用完毕这个函数,系统将进行尝试连接服务器。 第二步,在第一步讲过一个入口参数requestCallback(连接成功后的返调函数),比如我们定义一个函数void Connected(IAsyncResult result),在连接服务器成功后,系统会调用此函数,在函数里,我们要获取到系统分配的数据流传输对象(NetworkStream),这个对象是用来处理客户端与服务器端数据传输的,此对象由TcpClient获得,在第一步讲过入口参数state,如果我们传递了TcpClient进去,那么,在函数里我们可以根据入口参数state获得,将其进行强制转换TcpClient tcpclt = (TcpClient)result.AsyncState,接着获取数据流传输对象NetworkStream ns = tcpclt.GetStream(),此对象我建议弄成全局变量,以便于其他函数调用,接着我们将挂起数据接收等待,调用ns下的方法BeginRead,入口参数有5个,buff(数据缓冲),offset(缓冲起始序号),size(缓冲长度),callback(接收到数据后的返调函数),state(传递参数,一样,随便什么都可以,建议将buff传递过去),调用完毕函数后,就可以进行数据接收等待了,在这里因为已经创建了NetworkStream对象,所以也可以进行向服务器发送数据的操作了,调用ns下的方法Write就可以向服务器发送数据了,入口参数3个,buff(数据缓冲),offset(缓冲起始序号),size(缓冲长度)。 第三步,在第二步讲过调用了BeginRead函数时的一个入口参数callback(接收到数据后的返调函数),比如我们定义了一个函数void DataRec(IAsyncResult result),在服务器向客户端发送数据后,系统会调用此函数,在函数里我们要获得数据流(byte数组),在上一步讲解BeginRead函数的时候还有一个入口参数state,如果我们传递了buff进去,那么,在这里我们要强制转换成byte[]类型byte[] data= (byte[])result.AsyncState,转换完毕后,我们还要获取缓冲区的大小int length = ns.EndRead(result),ns为上一步创建的NetworkStream全局对象,接着我们就可以对数据进行处理了,如果获取的length为0表示客户端已经断开连接。 具体实现代码,在这里我建立了一个名称为Test的类: 2.服务器端: 相对于客户端的实现,服务器端的实现稍复杂一点,因为前面讲过,一个服务器端可以接受N个客户端的连接,因此,在服务器端,有必要对每个连接上来的客户端进行登记,因此服务器端的程序结构包括了2个程序结构,第一个程序结构主要负责启动服务器、对来访的客户端进行登记和撤销,因此我们需要建立2个类。 第一个程序结构负责服务器的启动与客户端连接的登记,首先建立TcpListener网络侦听类,建立的时候构造函数分别包括localaddr和port2个参数,localaddr指的是本地地址,也就是服务器的IP地址,有人会问为什么它自己不去自动获得本机的地址?关于这个举个很简单的例子,服务器安装了2个网卡,也就有了2个IP地址,那建立服务器的时候就可以选择侦听的使用的是哪个网络端口了,不过一般的电脑只有一个网络端口,你可以懒点直接写个固定的函数直接获取IP地址System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0],GetHostAddresses函数就是获取本机的IP地址,默认选择第一个端口于是后面加个[0],第2个参数port是真侦听的端口,这个简单,自己决定,如果出现端口冲突,函数自己会提醒错误的。第二步,启动服务器,TcpListener.Start()。第三步,启动客户端的尝试连接,TcpListener.BeginAcceptTcpClient,入口2个参数,callback(客户端连接上后的返调函数),state(传递参数,跟第二节介绍的一样,随便什么都可以,建立把TcpListener自身传递过去),第四步,建立客户端连接上来后的返调函数,比如我们建立个名为void ClientAccept(IAsyncResult result)的函数,函数里,我们要获取客户端的对象,第三步里讲过我们传递TcpListener参数进去,在这里,我们通过入口参数获取它TcpListener tcplst = (TcpListener)result.AsyncState,获取客户端对象TcpClient bak_tcpclient = tcplst.EndAcceptTcpClient(result),这个bak_tcpclient我建议在类里面建立个列表,然后把它加进去,因为下一个客户端连接上来后此对象就会被冲刷掉了,客户端处理完毕后,接下来我们要启动下一个客户端的连接tcplst.BeginAcceptTcpClient(new AsyncCallback(sub_ClientAccept), tcplst),这个和第三步是一样的,我就不重复了。 第二个程序结构主要负责单个客户端与服务器端的处理程序,主要负责数据的通讯,方法很类似客户端的代码,基本大同,除了不需要启动连接的函数,因此这个程序结构主要启动下数据的侦听的功能、判断断开的功能、数据发送的功能即可,在第一个程序第四步我们获取了客户端的对象bak_tcpclient,在这里,我们首先启动数据侦听功能NetworkStream ns= bak_tcpclient.GetStream();ns.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);这个跟我在第二节里介绍的是一模一样的(第二节第10行),还有数据的处理函数,数据发送函数,判断连接已断开的代码与第二节也是一模一样的,不过在这里我们需要额外的添加一段代码,当判断出连接已断开的时候,我们要将客户端告知第一个程序结构进行删除客户端操作,这个方法我的实现方法是在建立第二个程序结构的时候,将第一个程序结构当参数传递进来,判断连接断开后,调用第一个程序结构的公开方法去删除,即从客户端列表下删除此对象。 第一个程序结构我们定义一个TSever的类,第二个程序结构我们一个TClient的类,代码如下:TSever类
本版本与网上其他资源不同之处在于,此版本可进行编辑,搜索,已进行内容识别扫描。可全选,可编辑,可剪切文字。 部分目录如下: 目录 第一篇预备知识 第1 章C++ 编程技术...................................................... 3 1-1 C++与C 语言的区别................................................... 4 1-1-1 文件扩展名的改变,.............................................. 4 1-1-2 简化输入/输出手段.............................................. 5 1-1-3 数据类型声明的改变,............................................ 5 1-1-4 动态内存分配运算符的使用....................................... 6 1-1-5 弓I 用(References) 类型, ··················•················•"'''8 1-1-6 const 语义的扩展................................................ 9 1-1-7 指针声明类型与对象类型相一致.................................. 13 1-1-8 int 与char 不再等价............................................. 13 1-1-9 结构数据类型的变化............................................ 13 1-1-10 数组和指针技术的不同......................................... 14 1-2 C++存储技术........................................................ 15 1-2 一I C++存储类型.................................................. 15 I6I7 ..... .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. •. .• •. .• .. .. .. .. .. .. .. .. .. .. .. .. . 期 符存 饰生 修的 取象 存对 廿廿 I2I32 ~3 c c 1-3 C++ 函数技术........................................................ 19 1-3-1 类的构造函数、析构函数与赋值函数,..... - ........ - .............. 19 1-3-2 在派生类中实现类的基本函数,................... _ ............... 29 1-3-3 内联函数技术,........ ................................... 30 3133 ..... .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. . 现 实 术的 技制 数机 函象 元对 友向 由 曰1. l -C 1 4 3337 ..... .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. . 术术 技技 承载 继重 的数 类函 l4l44 3 ~ 3840 ..... .. .. .. .. .. .. •. .• .. .. .. .. .. .. .. .. .. .. .. •. .• .. •. .• •. •• .• .. .. .. .. .. .. .. .. .. •. •• .• . 术 技 类 术象 技抽 载和 重数 符函 算虚 运纯 l4l34 4 1-5 小结...............................................
目 录 译者序 前言 第1章 对象的演化 1.1基本概念 1.1.1对象:特性十行为 1.1.2继承:类型关系 1.1.3多态性 1.1.4操作概念:OOP程序像什么 1.2为什么C++会成功 1.2.1较好的C 1.2.2采用渐进的学习方式 1.2.3运行效率 1.2.4系统更容易表达和理解 1.2.5“库”使你事半功倍 1.2.6错误处理 1.2.7大程序设计 1.3方法学介绍 1.3.1复杂性 1.3.2内部原则 1.3.3外部原则 1.3.4对象设计的五个阶段 1.3.5方法承诺什么 1.3.6方法应当提供什么 1.4起草:最小的方法 1.4.1前提 1.4.2高概念 1.4.3论述(treatment) 1.4.4结构化 1.4.5开发 1.4.6重写 1.4.7逻辑 1.5其他方法 1.5.1Booch 1.5.2责任驱动的设计(RDD) 1.5.3对象建模技术(OMT) 1.6为向OOP转变而采取的策略 1.6.1逐步进入OOP 1.6.2管理障碍 1.7小结 第2章 数据抽象 2.1声明与定义 2.2一个袖珍C库 2.3放在一起:项目创建工具 2.4什么是非正常 2.5基本对象 2.6什么是对象 2.7抽象数据类型 2.8对象细节 2.9头文件形式 2.10嵌套结构 2.11小结 2.12练习 第3章 隐藏实现 3.1设置限制 3.2C++的存取控制 3.3友元 3.3.1嵌套友元 3.3.2它是纯的吗 3.4对象布局 3.5类 3.5.1用存取控制来修改stash 3.5.2用存取控制来修改stack 3.6句柄类(handleclasses) 3.6.1可见的实现部分 3.6.2减少重复编译 3.7小结 3.8练习 第4章 初始化与清除 4.1用构造函数确保初始化 4.2用析构函数确保清除 4.3清除定义块 4.3.1for循环 4.3.2空间分配 4.4含有构造函数和析构函数的stash 4.5含有构造函数和析构函数的stack 4.6集合初始化 4.7缺省构造函数 4.8小结 4.9练习 第5章 函数重载与缺省参数 5.1范围分解 5.1.1用返回值重载 5.1.2安全类型连接 5.2重载的例子 5.3缺省参数 5.4小结 5.5练习 第6章 输入输出流介绍 6.1为什么要用输入输出流 6.2解决输入输出流问题 6.2.1预先了解操作符重载 6.2.2插入符与提取符 6.2.3通常用法 6.2.4面向行的输入 6.3文件输入输出流 6.4输入输出流缓冲 6.5在输入输出流中查找 6.6strstreams 6.6.1为用户分配的存储 6.6.2自动存储分配 6.7输出流格式化 6.7.1内部格式化数据 6.7.2例子 6.8格式化操纵算子 6.9建立操纵算子 6.10输入输出流实例 6.10.1代码生成 6.10.2一个简单的数据记录 6.11小结 6.12练习 第7章 常量 7.1值替代 7.1.1头文件里的const 7.1.2const的安全性 7.1.3集合 7.1.4与C语言的区别 7.2指针 7.2.1指向const的指针 7.2.2const指针 7.2.3赋值和类型检查 7.3函数参数和返回值 7.3.1传递const值 7.3.2返回const值 7.3.3传递和返回地址 7.4类 7.4.1类里的const和enum 7.4.2编译期间类里的常量 7.4.3const对象和成员函数 7.4.4只读存储能力 7.5可变的(volatile) 7.6小结 7.7练习 第8章 内联函数 8.1预处理器的缺陷 8.2内联函数 8.2.1类内部的内联函数 8.2.2存取函数 8.3内联函数和编译器 8.3.1局限性 8.3.2赋值顺序 8.3.3在构造函数和析构函数里隐藏行为 8.4减少混乱 8.5预处理器的特点 8.6改进的错误检查 8.7小结 8.8练习 第9章 命名控制 9.1来自C语言中的静态成员 9.1.1函数内部的静态变量 9.1.2控制连接 9.1.3其他的存储类型指定符 9.2名字空间 9.2.1产生一个名字空间 9.2.2使用名字空间 9.3C++中的静态成员 9.3.1定义静态数据成员的存储 9.3.2嵌套类和局部类 9.3.3静态成员函数 9.4静态初始化的依赖因素 9.5转换连接指定 9.6小结 9.7练习 第10章 引用和拷贝构造函数 10.1C++中的指针 10.2C+十中的引用 10.2.1函数中的引用 10.2.2参数传递准则 10.3拷贝构造函数 10.3.1传值方式传递和返回 10.3.2拷贝构造函数 10.3.3缺省拷贝构造函数 10.3.4拷贝构造函数方法的选择 10.4指向成员的指针(简称成员指针) 10.5小结 10.6练习 第11章 运算符重载 1

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌托邦2号

博文不易,支持的请给予小小打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值