Error

C++博客 首页 新随笔 联系 聚合 管理
  217 Posts :: 61 Stories :: 32 Comments :: 0 Trackbacks

#

grant 权限 on 对象 to 用户

一、grant 普通数据用户,查询、插入、更新、删除 数据库中所有表数据的权利。 
grant select on testdb.* to 
grant insert on testdb.* to 
grant update on testdb.* to 
grant delete on testdb.* to

或者,用一条 MySQL 命令来替代: 
grant select, insert, update, delete on testdb.* to

二、grant 数据库开发人员,创建表、索引、视图、存储过程、函数。。。等权限。 
grant 创建、修改、删除 MySQL 数据表结构权限。 
grant create on testdb.* to ;
grant alter  on testdb.* to ;
grant drop   on testdb.* to ;

grant 操作 MySQL 外键权限。 
grant references on testdb.* to ;

grant 操作 MySQL 临时表权限。 
grant create temporary tables on testdb.* to ;

grant 操作 MySQL 索引权限。 
grant index on  testdb.* to ;

grant 操作 MySQL 视图、查看视图源代码 权限。 
grant create view on testdb.* to ;
grant show   view on testdb.* to ;

grant 操作 MySQL 存储过程、函数 权限。 
grant create routine on testdb.* to ;  -- now, can show procedure status
grant alter  routine on testdb.* to ;  -- now, you can drop a procedure
grant execute        on testdb.* to ;

三、grant 普通 DBA 管理某个 MySQL 数据库的权限。 
grant all privileges on testdb to

其中,关键字 “privileges” 可以省略。 
四、grant 高级 DBA 管理 MySQL 中所有数据库的权限。 
grant all on *.* to

五、MySQL grant 权限,分别可以作用在多个层次上。 
1. grant 作用在整个 MySQL 服务器上: 
grant select on *.* to ; -- dba 可以查询 MySQL 中所有数据库中的表。
grant all    on *.* to ; -- dba 可以管理 MySQL 中的所有数据库

2. grant 作用在单个数据库上: 
grant select on testdb.* to ; -- dba 可以查询 testdb 中的表。

3. grant 作用在单个数据表上: 
grant select, insert, update, delete on testdb.orders to ;

4. grant 作用在表中的列上: 
grant select(id, se, rank) on testdb.apache_log to ;

5. grant 作用在存储过程、函数上: 
grant execute on procedure testdb.pr_add to 
grant execute on function  testdb.fn_add to

六、查看 MySQL 用户权限 
查看当前用户(自己)权限: 
show grants;

查看其他 MySQL 用户权限: 
show grants for ;

七、撤销已经赋予给 MySQL 用户权限的权限。 
revoke 跟 grant 的语法差不多,只需要把关键字 “to” 换成 “from” 即可: 
grant  all on *.* to   ;
revoke all on *.* from ;

八、MySQL grant、revoke 用户权限注意事项 
1. grant, revoke 用户权限后,该用户只有重新连接 MySQL 数据库,权限才能生效。 2. 如果想让授权的用户,也可以将这些权限 grant 给其他用户,需要选项 “grant option“ 
grant select on testdb.* to with grant option;

这个特性一般用不到。实际中,数据库权限最好由 DBA 来统一管理。

posted @ 2014-11-14 19:29 Enic 阅读(218) | 评论 (0)编辑 收藏

string传值方式效率肯定是有问题的,如果使用引用方式,则必须提供原生指针接口,否则会有异常

void Test(const std::string& strParam)
{
strParam.c_str();
}
void Test(const char* szParam)
{
}
void Test()
{
Test(nullptr);
int i = 0;
i++;
std::string strA = "---";
}
posted @ 2014-11-07 14:47 Enic 阅读(1265) | 评论 (0)编辑 收藏

Jsp如何转换为Servlet
jsp的底层技术是servlet,他们的生命周期是相同的。服务器负责实例化jsp/servlet,激活init()方法,准备处理客户端请求。可以通过编写service()方法处理自己的事物逻辑,或者自己编写doGet()、doPost()方法。服务器激活destroy()方法时,jsp/servlet被销毁,启动gc使用finalize()方法清理内存。
jsp会被转换到适当的servlet代码,即一个.java文件。许多应用服务器保存生成的.java文件,一旦他转换到.java文件被编译为字节码.class。.class文件被支持输出成html文档返回给客户端。
HttpServlet基本结构
编写一个HttpServlet时,通常需要许该的方法:
Void init(ServletConfig sc) throws ServletException;
Void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
Void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
Void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
html表单(form)拥有一种成为“method”属性,用于定义如何想服务器发送数据。Get用于将数据追加入url的方式向服务器发送数据。Post用于将数据打包发送给服务器。
理解jsp应用模型
什么是应用模型:对jsp如何相互交互的一种描述。
1.简单模型:单一的jsp构成。
2.N层模型:添加了类似JavaBean的服务端资源。
3.松散耦合模型:允许位于远程系统的jsp做对等交互,或者拥有一种C/S关系。
通过html或者xml的http通讯,每个jsp应用都与其他jsp应用保存隔离。
4.包含请求模型:一个jsp可以负责请求和响应,但是包含其他jsp的输出。通过include行为实现。
5.转发请求模型:重定向,通过forward行为实现。
理解Jsp的布局
标签可以是html标签,也可以是jsp标签。
jsp标签可以有作用域 <jsp: useBean name="myClass" class="SimpleClass" scope="page" />
注释:
1.html:<!-- -->
2.jsp: <%-- --%>
3.java: // or /**/
jsp解剖:
html代码----负责页面整个布局和外观
jsp标签----负责scriptlet、表达式、声明、行为和指令
jsp隐式对象----例如:request对象、response对象、session对象、config对象
JavaBean----实现事物逻辑
理解jsp标签元素
jsp有两种形式:<%%>、<jsp:tagid/>
1.jsp声明标签用法:(定义变量)
<%!
declaration_statement(s)
%>
<jsp:declaration>
</jsp:declaration>
2.jsp表达式标签用法:(引用变量)
<%=expression%>
<jsp: expression> expression </jsp: expression>
3.jsp scriptlet标签用法:(编写java代码,差不多就是用标签把java代码包起来,标签会截断标签)
<% 
boolean isPasswordAnyGood(String password)
{
return false;
}
%>
<%
if(isPasswordAnyGood("123"))
{
%>
<H1>Welcome<H1/>
<%
}
else
{
%>
<H1>Press Login</H1>
<%
}
%>
4.jsp指令标签:(预处理命令之类)
<%@ page import="com.macmilan.jspln24.*" %>
5.jsp行为标签(允许jsp使用java创建对象,包含jsp,转发jsp,java交互)
六种:useBean setProperty getProperty include forward plugin
Jsp内置对象用法:
内置对象提供来自浏览器的请求并且动态响应的功能:
request out response pageContext session application config page
JavaBean规范(这是个大议题,据说有两种用法,一种可以直接无视,把javabean当不同java类在jsp中导入来用。一种是遵循javabean规范使用标签来访问)
7
posted @ 2014-11-04 16:25 Enic 阅读(148) | 评论 (0)编辑 收藏

首先一个原则是这样:被用来delete的指针,一定是new出来的。
在设计智能指针的时候发现,如果采用delete this机制,到最后可能会有严重的问题。
假设有这样的多重继承:
class CA{};
class CB{};
class CC{};
class CABC : public CA, public CB, public CC
{};
CABC* pAbc = new CABC;
CA* pA = pAbc;
CB* pB = pAbc;
CC* pC = pAbc;
A B C指针的值都是不一样的,如果是简单的使用delete 来删除,问题就大条了,pB pC的地址和new出来的pAbc不一样,这样就出现堆错误。
如果设计入侵式RefObj问题就大条了:
class Ref
{
public:
virtual void Test()
{
std::cout << this << std::endl;
}
int m_i;
};
class CA : public virtual Ref{};
class CB : public virtual Ref{};
class CC : public virtual Ref{};
class CABC : public CA, public CB, public CC{};
         C* pAbc = new CABC;
CA* pA = pAbc;
CB* pB = pAbc;
CC* pC = pAbc;
通过打印可以看到,虽然ABC 在基类Test打印出来的this地址一致,但是和实际正确的delete指针(new出来的)是不一致的,这就准备堆错误了,,,
这基本意味着使用入侵式样Ref的类,使用了多重继承就是作死。除非和com的思想一样,Ref不是作为基类处理,而是作为纯虚接口,但是又会使得编码复杂化,基本上全部要用组合来替代继承
class IXXX;
class CXXX;
class IYYY;
class CYYYXXX : public CXXX, public IYYY;
这样的用法无法实现,应为没有实现IXXX接口,是一个虚基类,实现了在调用的时候也是各种基类未指定的错误。

奇怪的是shared_ptr没有这个限制,猜测是在第一次从裸指针构造出来的时候,保留了原始地址,生成了一个析构函数,会在shared_ptr之间共享,直到最后一个对象析构的时候调用.
实际观察std::shared_ptr的实现,发现其构造函数不是控制一定要转换成T指定的类型,而是给入参数的实际类型,后面的管理也分两个部分,一层得到根据T转换后的值,实际的生命周期管理得到仍然是实际类型,所以他delete的时候还是当初给定的值。
所以,感觉智能指针不是什么好东西,背后的细节太多,一个不小心就死翘翘



class RefObj
{
public:
RefObj() : m_i(0){}
public:
void Increate(){m_i++;}
void Decreate()
{
if(0 == --m_i)
{
delete this;
}
}
private: 
int m_i;
};
class BaseA : public virtual RefObj {public: int m_iA;};
class BaseB : public virtual RefObj {public: int m_iB;};
class BaseC : public virtual RefObj {public: int m_iC;};
class CBaseABC : public BaseA,
public BaseB,
public BaseC
{
};
void Test(BaseB* pB)
{
pB->Decreate();
}
int _tmain(int argc, _TCHAR* argv[])
{
ETcpSocket tcpSocket;
CBaseABC* pABC = new CBaseABC;
pABC->Increate();
BaseB* pB = pABC;
Test(pB);
return 0;
}



始终还是觉得相比shared_ptr,RefObj这种,还是有部分场景更加格式,至少不用担心类型转换变得异常麻烦,,,
后发现如下实现方式:
class IRefObj
{
public:
virtual void __DecRef() = 0;
};
template<typename TType>
class TRefObj : public IRefObj
{
public:
void __DecRef()
{
delete reinterpret_cast<TType*>(this);
}
};
class CRefTest : public TRefObj<CRefTest>
{
};
class CRefTestA : public CRefTest
{
};
class CTestA {int i;};
class CTestB {int j;};
class CTestC : public CTestB, public CRefTestA, public  CTestA
{
};
int _tmain(int argc, _TCHAR* argv[])
{
CTestC* pTC = new CTestC;
CTestB* pTB = pTC;
CRefTestA* pRTA = pTC;
CTestA* pTA = pTC;
pRTA->__DecRef();

这个时候CRefTest是预期额值,怀疑可能和编译器有关,VS测试OK,gcc没去实验。
分析了一下原因:C++保证单根继承的时候基类和派生类地址是一样的,如果是多重继承,那么也保证和最深的根父类地址样,顺便的,从最深的根节点,选一路下来是安全的。
class CRefTest : public TRefObj<CRefTest>所以约定TRefObj<CRefTest>这玩意和永远和接口在一个级别,可以保证IRefObj永远是最深的根,就安全了?
目前只在控件设计的时候用RefObj吧,,,坑的比较深,,,
posted @ 2014-10-16 16:29 Enic 阅读(742) | 评论 (2)编辑 收藏

技术路线的选择重要但不具有决定性(转载)

转自 http://blog.csdn.net/myan/article/details/3247071  
 
孟岩 2008 年的文章,现在看来还是挺有启发, 送给大家,也送给自己。

最近微软在技术上连续有大动作,在PDC上发布了Windows Azure云计算平台,预告了Visual Studio 2010、.NET 4.0和C# 4.0。如果放在几年前,我相信微软粉丝们一定是欢声雷动,不过这次情况有点不太一样,在网上看到有人在抱怨微软技术更新速度太快而且四面出击,还有人扬言要改弦更张,投奔Linux或者Java阵营。我本人也收到一封来信,写信人大意是说自己大学时选择.NET路线,一路跟下来很辛苦,2.0还没学好,人家已经4.0预览了,感到很困惑,问我该怎么办。老实讲,这样的问题我无法回答,每个人具体情况不同,所应该采取的态度和解决方案也不同。从我自己来讲,其实技术路线问题也曾长时间地困扰我,所以我想把我现在的一些想法摊出来跟大家分享一下。罗列如下:

1. 根据我长期的观察,做开发技术的人按照其人生路线设计,可以分成几类。第一类是把自己的命运寄托在一项事业上。这样的人知道自己想干什么,而且有能力把技术当工具来实现自己的想法和事业。这里所说的事业是广义的,并不是说你非要自己开公司当老板,而是说你认可一件事情,比如促进人们交流和言论自由,带给大家更多娱乐,提升大众身心健康水平,增强国家国防实力,或者提升某个行业的信息化水平,然后你能够以技术为手段,在这个事情上做出成绩。这种人做着自己认为值得一生投入的事情,愿意领略这一追求带来的人生起伏并且无怨无悔,我认为这是做技术的最高层次。第二类是把自己的命运寄托在组织和团队上。这种人虽然不知道自己到底想干什么,但是技术水平出色,而且综合素质突出,勇于变化,能够把技术当敲门砖进入某个优秀的团队,以团队的目标为目标,依据团队的需求而转型或者坚持,跟团队一起干出一番成绩。这类人有令人羡慕的职业背景,在大公司里高薪厚禄,生活比较安定舒适,但是中年以后会经常自问到底做了什么自己想做的事情为自己过于风平浪静的人生感到惆怅。但总的来说,这个层次也是比较高的。第三种是把命运寄托在技术上。这种人有能力成为技术的专家,然后就希望奇货可居,待价而沽,把技能当商品出售谋求富足人生。这种人没有大的人生目标,不想把自己的命运跟企业和组织绑定在一起,也不愿意做什么改变,只是满足于技术高手的层面,寄希望于其技术专长能够长期值钱,有点投机主义者的意思。第四种是还处于出卖劳动力的阶段,在这里就不多说了。

我想说的是,在过去很多年里,很多技术人实际上是把自己定位在第三种人里。而实际上,只有成为第一类和第二类人,才算达到了比较成功的状态。第三类人实际上最危险,因为技术的变迁不但是可能的,而且是一定的。他们要么马上被淘汰,要么追得老了累了追不动了以后被淘汰,被淘汰只是时间问题。 因此,如果你认为某个软件技术的兴起或者衰落对你个人的职业生涯构成了决定性的影响,那么你可能正走在错误的路线上,应当尽快改弦更张。

2. 对个体软件人来说,什么是核心竞争力?不是时间差,不是技术,不是基本功,不是什么思想,也不是聪明脑瓜,而是你独特的个性知识经验组合。

有人看到新技术出来了,急急忙忙赶上去尝鲜,以为自己快人一步,就能如何如何,实际上这种想法根本不靠谱,最多在论坛博客上风光两天,等这项技术投入实际应用以后一点便宜也占不到。

有人把某个技术、框架、平台研究得里外通透,以为这样就能奇货可居。实践中,这种人能红火一时,但很难超过5年。这是现阶段技术发展生命周期所决定的。此外,现在越来越多人意识到了,能够靠读书看文章读代码做练习学会的东西没什么门槛,智力正常的人只要愿意花功夫,都不难达到你的程度。有的人认为,自己有能力驾驭技术潮流,哪个红学哪个。我在技术行业里不敢说阅人无数,见过的高手上百是有的,说句不怕得罪人的话,能够连续抓住两个以上的潮流并且始终处于领先位置的人及其少见,一只手就数的过来。更常见的情况是,上一个阶段的成功会成为下一个阶段的障碍,所谓随机应变屹立潮头之说,往往只不过是当红小生给自己壮胆的狂言,时过境迁之后,他就只能听着新一代当红小生的豪言壮语而默默苦笑。

有人强调基本功,这是对的。在任何技术性行业里都一样,基础打得多深,上面就能造得多高。现实中,基本功扎实的人很少见,这跟中国教育的弊病有关,所以基本功好的人,一般应变能力强,学习速度快,比较受欢迎。但说基本功是核心竞争力,还是没有抓住本质。我们经常能看到基本功差不多的两个人,一个发展的很好,一个发展平平,这表明基本功成功职业生涯的是重要条件,但不是决定因素。

有人强调这个那个思想,实际上软件行业里的伟大的思想就那么屈指可数的几个,窗户纸一点就破,其他衍生出来的思想,就跟技术风潮一样,各领风骚三五年,成不了你核心竞争力。

还有人强调自己的智商,聪明脑瓜,觉得自己比别人聪明,自己的聪明是核心竞争力。大学生、刚毕业的人持这个观点的比较多,然而有过人生阅历以后,自然会对这种观点不以为然。本质上这是因为社会对于“聪明”的定义与学校不同,一个解题高手在学校里可能是受人仰慕的聪明脑瓜,但在职业人生中则可能是个大傻蛋。我们身边很多人走了一条不尽如意的人生道路,往往不是因为他们不够“聪明”,而是因为他们太“聪明”了,聪明反被聪明误。我想这也是为什么人们要发明“智慧”这个词以区别于“聪明”的原因。另外,关于这种观点,还有一点不得不指出,那就是在软件这个行当里,一般聪明就可以了,绝顶聪明占不到多少便宜。

那么核心竞争力是什么?我观察圈子里很多成功和不成功的技术人,提出一个观点,那就是个人的核心竞争力是是他独特的个性知识经验组合。这个行业里拥挤着上百万聪明人,彼此之间真正的不同在哪里?不在于你学的是什么技术,学得多深,IQ多少,而在于你身上有别人没有的独特的个性、背景、知识和经验的组合。如果这种组合,1,绝无仅有;2,在实践中有价值,3,具有可持续发展性,那你就具备核心竞争力。因此,当设计自己的发展路线时,应当最大限度地加强和发挥自己独特的组合,而不是寻求单项的超越。而构建自己独特组合的方式,主要是通过实践,其次是要有意识地构造。关于这个观点,话题太大,我不打算赘述。

3. 虽然技术路线的选择不是核心竞争力,也不应该具有决定性,但对于个人职业路线还是具有比较重要的影响力。但这并不是说,我们应该煞有介事地把自己归于Java或者.NET技术阵营,整天捧本书吭哧吭哧啃。正确的态度应该是着重于你要干的事情,然后认真把这件事情做好,通过必要的学习将所需的知识体系构筑完整,在整个过程中及时更新知识体系。只有心理没谱的人,才会为新技术的推出感到惶恐,因为他不知道自己要干什么,也就不知道自己要学什么,看到什么东西出来了都以为如果不学就会落伍,才会觉得是个压力,日积月累,才会痛苦彷徨嚷嚷怎么办。相反,如果你很清楚地知道自己要做什么,就会发现,其实必须及时更新的知识变化并不频繁,大多数新鲜玩意根本不在自己关注范围内,任他三仙落地,五佛升天,与我何干?因此完全可以安步当车,稳扎稳打。

4. 几年前我刚加入CSDN的时候,.NET和Java之争是最热门的话题。现在回过头看,其实当时无论你选择那条路,如果认真做下去,搞些实事,别玩虚活的话,现在都应该有成就了。当然,客观上来说,这几年微软技术变化是比较快,弯弯绕得比较多,相比之下,如果当时你选择的是Java,可能这几年过的比较幸福一些,这是事实。我对此并不是没有自己的看法,但是这毕竟不是多么大的问题,实际上Java这几年折腾得也够猛,只不过作为一个比较开放的领域,Java为其追随者保留了更多的自由度,而微软的追随者大多数有一种被驱赶的感觉而已。话说回来, 微软的技术变革并不是没有章法的,其今天的技术架构,早在2003年就已经明明白白地公诸于众,只不过因为某些微妙的原因,一些微软跟随者这几年被带着兜了一些圈子,浪费了一些精力,比较辛苦。不过,现在.NET技术体系的尘埃基本落定,从体系结构上看,相对稳定的时期已经到来,投资微软技术可以放心。

5. 不过我相信未来不同技术流的应用领域会出现一些明显的分化。在中国,涉及国防、国家安全、命脉产业和关键行业的服务端要害系统,国产化改造是阻挡不住的潮流,长期来看,开源和Java将在这个领域占据主导低位。其他的领域,随着微软技术变革的的大势确定,相信微软的优势不可小觑。这里没有考虑中国政府可能做出的产业调整政策。这次微软黑屏事件,无论是否出于微软本意,其最大的效果在于向有关部门展示了一下其信息战力,中国政军内部有关机构对此不可能不加以警惕,这是否会引起中国国内IT产业政策的调整,现在还不得而知。但我相信,微软系统恐怕将在不长的时间里与中国关键要害领域的核心系统彻底说拜拜。未来中国IT系统的格局,很有可能是居庙堂之高则清一色Linux/Java,处江湖之远则Windows占主导。

6. 几年前还有一个热烈争论,就是Java和C#之争。现在实际上尘埃已经落定,两个语言的定位已经分道扬镳。Java实际上已经落实了成为系统语言的诺言,在现在的计算机体系结构上,Java与15年前的C一样,可以成为构造基础设施的利器,而且其性能相当不错,完全突破了之前人们对虚拟机语言的认识局限性。我相信在未来,Java将有效地侵蚀C语言的一些曾经以为千秋万代永不变色的地盘。虽然同时Java也在向上发展,但是其力度与C#不可同日而语。相反,C#主要是在往上发展,即将成为超级瑞士军刀,微软版十全大补膏,所有于应用开发有意义的特性都要加上,从编程语言发展来看,它将成为一株奇葩。作为一个编程语言的爱好者,我正饶有兴致地注视着史上特性最丰富语言C#的发展动向。但是,不得不指出,C#的弱点在脚跟。自从用它开发Longhorn Avalon失败以后,微软暂时放弃了让C#成为系统语言的努力,专心专意让C#变成应用开发领域的超级无敌霸王3000,而在核心领域,仍然是C++、COM当关。这就出现了有趣的局面,在可见的未来,微软体系内真正的核心软件基础设施,还是将由微软自己用C++来构造,而组合装配的应用开发,则由C#完成。VB和CLR平台上的其他动态语言都不会有太多机会,因为C#将穷尽神智正常者一切关于语言的幻想。

以上几点,如果有人现在要选择技术路线,可以参考一下。但切记,技术路线的选择重要,但不具有决定意义。

posted @ 2014-10-14 10:16 Enic 阅读(212) | 评论 (0)编辑 收藏

基于WinDbg的内存泄漏分析
在前面C++中基于Crt的内存泄漏检测一文中提到的方法已经可以解决我们的大部分内存泄露问题了,但是该方法是有前提的,那就是一定要有源代码,而且还只能是Debug版本调试模式下。实际上很多时候我们的程序会用到第三方没有源代码的模块,有些情况下我们甚至怀疑系统模块有内存泄露,但是有没有证据,我们该怎么办? 这时我们就要依靠无所不能的WinDbg了。

WinDbg的!heap命令非常强大,结合AppVerifier可以对堆(heap)内存进行详细的跟踪和分析, 我们接下来对下面的代码进行内存泄漏的分析:
// MemLeakTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
    char* p1 = new char;
    printf("%p\n", p1);

    char* pLargeMem = new char[40000];

    for(int i=0; i<1000; ++i)
    {
        char* p = new char[20];
    }
    
    system("pause");

    return 0;
}

首先下载安装AppVerifier, 可到这里下载, 把我们需要测试的程序添加到AppVerifier的检测列表中, 然后保存。

注: 我们这里用AppVerifier主要是为了打开页堆(page heap)调试功能,你也可以用系统工具 gflags.exe 来做同样的事。 

双击运行我们要调试的MemLeakTest.exe, 效果如下:


然后将WinDbg Attach上去, 输入命令 !heap -p -a 0x02FC1FF8,结果如下:
0:001> !heap -p -a 0x02FC1FF8
    address 02fc1ff8 found in
    _DPH_HEAP_ROOT @ 2f01000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 2f02548:          2fc1ff8                1 -          2fc1000             2000
    5a8c8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77485c4e ntdll!RtlDebugAllocateHeap+0x00000030
    77447e5e ntdll!RtlpAllocateHeap+0x000000c4
    774134df ntdll!RtlAllocateHeap+0x0000023a
    5b06a65d vrfcore!VfCoreRtlAllocateHeap+0x00000016
    5a92f9ea vfbasics!AVrfpRtlAllocateHeap+0x000000e2
    72893db8 MSVCR90!malloc+0x00000079
    72893eb8 MSVCR90!operator new+0x0000001f
    012c1008 MemLeakTest!wmain+0x00000008 [f:\test\memleaktest\memleaktest\memleaktest.cpp @ 11]
    77331114 kernel32!BaseThreadInitThunk+0x0000000e
    7741b429 ntdll!__RtlUserThreadStart+0x00000070
    7741b3fc ntdll!_RtlUserThreadStart+0x0000001b

怎么样, 神奇吧?我们当分配该地址内存时的堆栈(stack)被完整地打印了出来。

当然有人很快会说:这是你知道内存地址的情况, 很多情况下我们是不知道该地址的,该如何分析?

对于这种情况, 我们首先需要明确一些概念, 我们new出来的内存是分配在堆上, 那一个进程里究竟有多少个堆, 每个模块都有自己单独的堆吗?实际上一个进程可以有任意多个堆,我们可以通过CreateHeap创建自己单独的堆, 然后通过HeapAlloc分配内存。 我们new出来的内存是crt(C运行库)分配的, 那就涉及到crt究竟有多少个堆了? crt有多少个堆由你编译每个模块(Dll/Exe)时的编译选项决定, 如果你运行库选项用的是/MD, 那就和其他模块共享一个堆; 如果用/MT, 那就是自己单独的堆。大部分情况下我们会用/MD,这样我们在一个模块里new内存, 另一个模块里delete不会有问题, 因为大家共享一个堆。

明确这些概念之后, 我们看看我们的测试程序有多少个堆, 输入!heap -p
0:001> !heap -p

    Active GlobalFlag bits:
        vrf - Enable application verifier
        hpa - Place heap allocations at ends of pages

    StackTraceDataBase @ 00160000 of size 01000000 with 00000034 traces

    PageHeap enabled with options:
        ENABLE_PAGE_HEAP
        COLLECT_STACK_TRACES

    active heaps:

    + 1160000
        ENABLE_PAGE_HEAP COLLECT_STACK_TRACES 
      NormalHeap - 1300000
          HEAP_GROWABLE 
    + 1400000
        ENABLE_PAGE_HEAP COLLECT_STACK_TRACES 
      NormalHeap - 16b0000
          HEAP_GROWABLE HEAP_CLASS_1 
    + 2360000
        ENABLE_PAGE_HEAP COLLECT_STACK_TRACES 
      NormalHeap - 1280000
          HEAP_GROWABLE HEAP_CLASS_1 
    + 2f00000
        ENABLE_PAGE_HEAP COLLECT_STACK_TRACES 
      NormalHeap - 31d0000
          HEAP_GROWABLE HEAP_CLASS_1 
可以看到我们的测试程序一共有4 个堆。

接下来我们的问题就是确定哪个是我们的crt堆, 也就是我们需要分析每个堆创建时的堆栈(stack)情况.

我们接下来分析最后一个堆, handle是2f00000, 输入!heap -p -h 02f00000 分析该堆的内存分配情况
0:001> !heap -p -h 02f00000
    _DPH_HEAP_ROOT @ 2f01000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
        02f01f04 : 02f09000 00002000
        02f02e38 : 02f69000 00002000
        037e2548 : 03892000 00002000
        037e2514 : 03894000 00002000
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
        02f01f6c : 02f05de8 00000214 - 02f05000 00002000
        02f01f38 : 02f07800 00000800 - 02f07000 00002000
        02f01ed0 : 02f0bde0 00000220 - 02f0b000 00002000
        02f01e9c : 02f0df50 000000ac - 02f0d000 00002000
        02f01e68 : 02f0ffe0 0000001f - 02f0f000 00002000
        02f01e34 : 02f11fd8 00000028 - 02f11000 00002000
        02f01e00 : 02f13fe0 0000001d - 02f13000 00002000
        02f01dcc : 02f15fc0 0000003a - 02f15000 00002000
        ....

可以看到该堆 _DPH_HEAP_ROOT 结构的地址是 2f01000,通过dt命令打印该结构地址
0:001> dt ntdll!_DPH_HEAP_ROOT CreateStackTrace 2f01000
   +0x0b8 CreateStackTrace : 0x0017cbe4 _RTL_TRACE_BLOCK

可以看到StackTrace的地址是 0x0017cbe4, 通过dds命令打印该地址内的符号
0:001> dds 0x0017cbe4 
0017cbe4  00178714
0017cbe8  00007001
0017cbec  000f0000
0017cbf0  5a8c8969 verifier!AVrfDebugPageHeapCreate+0x439
0017cbf4  7743a9e8 ntdll!RtlCreateHeap+0x41
0017cbf8  5a930109 vfbasics!AVrfpRtlCreateHeap+0x56
0017cbfc  755fdda2 KERNELBASE!HeapCreate+0x55
0017cc00  72893a4a MSVCR90!_heap_init+0x1b
0017cc04  72852bb4 MSVCR90!__p__tzname+0x2a
0017cc08  72852d5e MSVCR90!_CRTDLL_INIT+0x1e
0017cc0c  5a8dc66d verifier!AVrfpStandardDllEntryPointRoutine+0x99
0017cc10  5b069164 vrfcore!VfCoreStandardDllEntryPointRoutine+0x121
0017cc14  5a92689c vfbasics!AVrfpStandardDllEntryPointRoutine+0x9f
0017cc18  7741af58 ntdll!LdrpCallInitRoutine+0x14
0017cc1c  7741fd6f ntdll!LdrpRunInitializeRoutines+0x26f
0017cc20  774290c6 ntdll!LdrpInitializeProcess+0x137e
0017cc24  77428fc8 ntdll!_LdrpInitialize+0x78
0017cc28  7741b2f9 ntdll!LdrInitializeThunk+0x10
0017cc2c  00000000
0017cc30  00009001

现在我们可以看到该堆被Create时的完整堆栈了, 通过堆栈,我们可以看到该堆正是由crt创建的, 也就是说我们new的内存都分配在该堆内。

如果你觉得上面跟踪堆创建的过程太复杂,可以先忽略, 下面我们分析堆状态, 输入!heap -stat -h 0,它会分析所有堆的当前使用状态, 我们着重关注我们的crt堆02f00000:
Allocations statistics for
 heap @ 02f00000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    9c40 1 - 9c40  (52.66)
    14 3ea - 4e48  (26.38)
    1000 1 - 1000  (5.39)
    800 2 - 1000  (5.39)
    490 1 - 490  (1.54)
    248 1 - 248  (0.77)
    220 1 - 220  (0.72)
    214 1 - 214  (0.70)
    ac 2 - 158  (0.45)
    82 2 - 104  (0.34)
    6a 2 - d4  (0.28)
    50 2 - a0  (0.21)
    28 4 - a0  (0.21)
    98 1 - 98  (0.20)
    94 1 - 94  (0.19)
    8a 1 - 8a  (0.18)
    2e 3 - 8a  (0.18)
    41 2 - 82  (0.17)
    80 1 - 80  (0.17)
    7c 1 - 7c  (0.16)

我们可以看到排在第一位的是大小为0x9c40 (0n40000)的内存,分配了1次, 第二位的是大小为 0x14 (0n20) 的内存,分配了3ea (0n1002)次.
 回头再看我们的测试程序,怎么样? 是不是感觉很熟悉了。

输入!heap -flt s 0x9c40, 让WinDbg列出所有大小为0x9c40的内存:
0:001> !heap -flt s 0x9c40
    _DPH_HEAP_ROOT @ 1161000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
    _HEAP @ 1300000
    _DPH_HEAP_ROOT @ 1401000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
    _HEAP @ 16b0000
    _DPH_HEAP_ROOT @ 2361000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
    _HEAP @ 1280000
    _DPH_HEAP_ROOT @ 2f01000
    Freed and decommitted blocks
      DPH_HEAP_BLOCK : VirtAddr VirtSize
    Busy allocations
      DPH_HEAP_BLOCK : UserAddr  UserSize - VirtAddr VirtSize
        02f024e0 : 02fc63c0 00009c40 - 02fc6000 0000b000
    _HEAP @ 31d0000

可以看到, WinDbg帮我们找到了一个符合要求的分配, 它的UserAddr是02fc63c0, 该地址实际上就是代码char* pLargeMem = new char[40000]分配的地址, 按照开头的方法, 输入!heap -p -a 02fc63c0 
0:001> !heap -p -a 02fc63c0
    address 02fc63c0 found in
    _DPH_HEAP_ROOT @ 2f01000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 2f024e0:          2fc63c0             9c40 -          2fc6000             b000
    5a8c8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77485c4e ntdll!RtlDebugAllocateHeap+0x00000030
    77447e5e ntdll!RtlpAllocateHeap+0x000000c4
    774134df ntdll!RtlAllocateHeap+0x0000023a
    5b06a65d vrfcore!VfCoreRtlAllocateHeap+0x00000016
    5a92f9ea vfbasics!AVrfpRtlAllocateHeap+0x000000e2
    72893db8 MSVCR90!malloc+0x00000079
    72893eb8 MSVCR90!operator new+0x0000001f
    012c101e MemLeakTest!wmain+0x0000001e [f:\test\memleaktest\memleaktest\memleaktest.cpp @ 13]
    77331114 kernel32!BaseThreadInitThunk+0x0000000e
    7741b429 ntdll!__RtlUserThreadStart+0x00000070
    7741b3fc ntdll!_RtlUserThreadStart+0x0000001b

可以看到该堆栈就是我们
new char[40000]的堆栈, 用同样的方法, 我们可以分析出上面代码for循环中的1000次内存泄漏。

最后, 总结一下, 通过WinDbg结合AppVerifier, 我们可以详细的跟踪堆中new出来的每一块内存。 很多时候在没有源代码的Release版本中,在程序运行一段时间后,如果我们发现有大块内存或是大量同样大小的小内存一直没有释放,  我们就可以用上面的方法进行分析。有些情况下,我们甚至可以将 _CrtDumpMemoryLeaks()和WinDbg的!heap -p -a [address]命令结合起来使用, 由前者打印泄漏地址,后者分析调用堆栈,以便快速的定位问题。

posted @ 2014-10-14 09:57 Enic 阅读(258) | 评论 (0)编辑 收藏

客户端架构设计的简单总结
我们知道,客户端是相对服务端而言的,客户端程序相对普通应用程序,主要是增加了网络通讯功能。在这个移动和云存储的年代,大部分终端应用程序都有网络通讯功能, 所以都可以称为客户端。常见的客户端如浏览器,IM客户端, 网络会议客户端,邮件客户端,微博和微信客户端等...

通过观察,我们会发现所有的客户端基本是大同小异,都会包括一些相同的功能组件, 下面简单例举下:
通讯协议层

既然客户端都有网络功能,就会涉及到通讯方式和数据格式以及协议, 这三者不是完全独立,而是有机统一的。

首先说通讯方式,常见的通讯方式包括TCP,UDP, P2P和http(s), 很多时候我们不会用单一的通讯方式,而是多种通讯方式的结合。比如说TCP端口被封,走不通时,我们会转成尝试http(s)。IM中聊天文本走的是TCP, 由服务器转发,但是2个客户端之间的文件传输我们可能走的又是P2P了, 多个人之间的语音聊天, 我们走的又是UDP了。

其次说数据格式,常见的数据格式包括二进制编码,开源序列化协议和文本格式。
二进制一般是自定义的私有格式,通常对数值,我们会转成大头端,对字符串我们会用UTF8 编码,因为没有冗余数据,它的优点是不会浪费带宽;主要缺点是有硬编码的味道,不好扩充。
开源序列化协议这里主要是指google的protocal buffer,  现在很多公司都在用, 很多人基于它开发了自己的RPC框架。主要优点是数据小,使用简单而高效。
文本格式主要是指xml和json. 相对来说xml比较清晰和容易扩充,但是冗余数据比较多。json借助javascript对它语言层次的支持,感觉主要是前端人员使用的比较多。

最后再说协议,  协议和我们的应用相关联。比如邮件客户端,当然是走SMTP和POP3了; IM客户端的话,一般走XMPP了;  网络会议的话,可以走ITU的T.120协议, 也可以RFC 6501定义的XCON, 信令走SIP, 数据走RTP等。

通信协议层是整个客户端网络事件驱动的引擎,它可能会比较简单,也可能会很复杂。如果是基于XMPP的IM, 它可能会比较简单,因为基本上只需要一层文本协议的封包和解包就可以了。 当如果是基于T.120网络会议客户端,就会比较复杂,它数据包走的自定义的二机制格式,按照T.120协议的建议, 在通讯协议层又分了3层:TP, MCS和GCC。TP层主要封装数据传输的方式, 可以让上层无差别的区分TCP和http(s)。 MCS层主要提供多点传输功能, 它抽象出通道(channel)这个概念, 让不同session的数据进行逻辑隔离, 上层用户可以同时加入不同的通道来进行一对一和一对多的数据收发,并且通道中的数据有不同的优先级, 还有令牌这个机制。我们也可以在MCS层对数据进行加密和压缩, 还可以对上层的大数据包进行切包等。 GCC层主要封装会议的最基本逻辑,比如创建会议和加入session数据包的格式封装等, 让上层可以通过API调用而不用关心协议要求的数据包格式。不同的数据包会工作在不同的层次, 比如心跳包可能在底层TP层就被拦截了,它不要再往上层发,因为上面不用关心这个; 而有些数据包,则需要从底层往上层按照整个协议栈层层转发,当然每层都会剥离掉自己的协议头, 直至上层用户数据到达它的最终用户。

总之,通讯协议层封装了客户端和服务端的通讯方式及协议格式, 让上层用户不用关心底层的通信机制, 而只关注应用的接口事件。理论上我们可以在上层应用不做大调整的前提下,直接将网络会议客户端中的T.120协议成基于SIP的XCON。
功能组件

一个客户端程序通常是由很多功能模块组成,模块按功能来说可以分为基础组件和应用组件。

基础组件为应用组件提供的基础设施,基础组件是可以在不同的项目中重复使用的(比如界面控件库,2D渲染引擎Skia, 跨平台的网络和线程库等)。 
应用组件通常和我们当前的特定应用程序相关,比如我们的网络会议客户端包含的桌面共享模块, 文档共享模块,视频音频模块,文本聊天模块等。

应用模块本身分为带界面和无界面两种情况, 带界面的情况下我们通常会给组件提供一个容器窗口的句柄, 让组件自己在内部组织自己的界面。这种带界面的组件通常会为逻辑和界面的分离带来麻烦,在Window上实现一些半透明和动画效果也很难。 比如我们想提供跨平台的SDK来封装逻辑,这时我们会更倾向让应用组件采用无界面的模式,组件在跨平台层只封装逻辑和提供数据, 而把数据发到最上层界面层后再统一处理。

对功能组件我们的设计原则是尽量保持独立和可复用,最好能以仿COM方式动态升级而不用重新编译, 另外组件之间要保持层次性,避免双向或是循环依赖。
数据存储

客户端本身是处理和收发网络数据, 这里就涉及到对这些数据如何组织和存储的问题。这个通常会根据客户端的类型采用不同的处处理方式:
对于永久存储的数据,当然是保存成文件或是存入数据库,文件如xml, 数据库如ACCESS,  SQL server, mysql等。
临时数据当然是存入内存了,根据需要采用不同的数据结构, 组织格式如array,list, map, hashmap等。
还有一种是常见的数据保存方式是内存数据库,最常见是SQLite了, 内存数据库既能高效的分类保存大量数据, 又可以直接用基于SQL语句进行查询和处理, 比如foxmail客户端就是用SQLite存储的邮件信息。
还有一种是跨进程共享的数据,我们一般当然是内存映射文件了。比如我们有一个实时显示log的工具, 我们通常会在对方应用程序里分配共享内存的内存映射文件,所有的log都写到里面,然后在log工具程序里读取和显示。
当然还有一些数据可能存到网上去的, 常见的比如我们的云笔记, 这时数据分服务端数据和本地cache。

对于数据存储我们的设计原则是根据需要,选择简单高效的方式。比如我们在设计网络会议客户端时曾讨论要不要引入SQLite, 理想情况是引入内存数据库后各个组件和上层应用的数据都可以统一存储,采用数据驱动的方式,可以很方便的跟踪整个客户端的运行情况。但后来发现这种设计会把本来各自独立的组件通过数据库耦合在了一起,而且各组件的数据格式本身都很不要一样, 很难统一存储, 另外很多数据实际也只是临时数据, 没必要把简单的时间做复杂了。
客户端框架

客户端框架一般有两个作用:一是把所有的功能组件组织起来,进行统一的管理和展现; 二是实现整个客户端的主界面。客户端框架在协调各个组件时, 要注意避免让组件之间产生双向依赖, 而是应该让组件把事件通知给框架后由框架统一协调和处理, 所以客户端框架通常是整个客户端逻辑最复杂的部分。 一个好的客户端框架,通常会采用插件方式设计,可以动态插拔需要的组件。最好是可以根据服务端的配置, 动态下载和更新所需要的插件。

对于客户端框架, 我们的设计原则是低耦合和可扩展。基本上所有下层组件的改动都会影响到我们的客户端框架,客户的很多新需求和新组件也会导致框架产生坏味道, 这里我们设计时就要考虑如何让客户端框架能及时的适应这些变化。
界面库

客户端肯定会有界面,在Windows平台上,现在的界面大致分为以下几类:
基于Windows原始窗口控件句柄的C++ native 客户端, 基于.net的winform客户端,基于.net的WPF客户端,基于C++的DirectUI客户端, 以嵌入浏览器(如webkit)方式实现的客户端, 还有一类是基于Xaml的Metro客户端。

对于企业级大型安装应用,主要考虑的是开发效率,所以客户端还是以.net为主; 但是对于互联网企业, 更多的是要求客户端精简而高效, 所以还是以C++为主。 这里就设及到C++的两种界面库, 一种是基于windows窗口句柄和控件自绘机制的界面库,还有一中是基于DirectUI的界面库, 现在大一点的公司基本上都有自己的DirectUI界面库。基于修改Webkit方式实现的界面库可以通过Canvas和SVG动画等方式实现一些很炫的效果, 但是它和标准程序的用户体验还是有一定差距:一来web实现的控件跟Windows标准控件有很大不同, 二来web的流式布局和应用程序的网格布局也不一样。

对于界面,个人觉得这个东西变化实在太快了,所以我觉得应该尽量用界面和逻辑相分离的方式组织代码。
总结

对于客户端架构设计,个人觉得最大的原则就分层设计, 每层都封装一个概念并保持独立, 同时根据依赖倒置的原则, 站在上层客户的角度提供接口。软件工程里面的一条黄金定律:“任何问题都可以通过增加一个间接层来解决。

 


http://www.cppblog.com/weiym
posted @ 2014-10-14 09:46 Enic 阅读(253) | 评论 (0)编辑 收藏

最近有机会看号称是公司最核心的代码, 因为这个代码以前一直是美国那边保密的, 这么重要的代码会是啥样子?
真正拿到手大致看了一下后却挺失望的,因为该代码风格基本上是我刚毕业时的C++风格----带类的C,单从代码上看写的挺滥,里面没啥设计模式, 也没有用模板, 代码里面甚至一个函数可以写上近千行。
这么重要的代码, 竟然是这种风格,挺郁闷, 由此思考好的C++程序应该是什么风格?

C++因为本身支持多种范型设计(面向过程, 基于对象,面向对象,普通泛型,模板元编程等), 使得C++的程序风格和其他语言相比更加多种多样。所以有人评价C++像一把瑞士军刀, 什么功能都有, 你想拿它当什么刀使,它就能成为什么刀, 所以它很强大,强大的同时也意味着复杂。其他语言,比如Java/C#主要只支持面向对象,这样他们的风格就很统一, 无论是标准库,框架还是应用,都是以对象,接口和模式为主导。 但是C++程序就不一样了, 可以说C++程序风格没有固定的标准, 每个人根据他的经历和使用的框架,会有完全不一样的风格, 网上别人总结了一些C++程序风格:

1. 经典C++流:类是核心,例程多用C Runtime的,很少用模版,一般是正统教育的结果。
2. 古典C流:基本上当C用,偶尔用用对象,不使用异常,喜欢怀旧。
3. MFC流:秉承MFC的风格,主要使用MFC/ATL对象和Win32 API,不喜欢STL,用很多的宏把IDE的语法提示模块折磨到崩溃。
4. Portable流:以C Runtime和STL为主要工具,使用类和模版,不跨平台毋宁死。
5. Functional流:以模版和STL为主要武器,大量使用函数式语言的设计方法,并号称这才是真正的C++。
6. Win32流:多使用全局函数,偏爱Win32 API,但不排斥C Runtime,通常喜欢轻量级的程序,所以身材也比较苗条。
7. Java流:全面使用Java的风格,不能容许任何全局成员,但允许使用STL的集合类,写很多叫Factory的类。
8. COM流:喜欢AddRef()和Release(),大量使用接口,隐藏一切可以隐藏的东西,诵经的时候要把上帝替换成COM。
9. 戒律流:追求完美的C++程序,计较每一个const和throw(),极力避免不安全的cast,随身一定要带一本ISO C++手册。
10. 混沌流:其程序无常形,无恒道,变幻莫测,吾不知其名。 

上面确实总结了我们常见的一些C++程序风格,相信大部分C++程序员都可以再里面找到自己曾经或现在的影子。另外每个人C++程序风格不是一成不变的,随着他的项目经历会不断的变化。比如一般人刚毕业时的风格都是带类的C,代码风格偏向面向过程; 后来随着对面向对象的深入, 慢慢地会使用模式和接口来设计,此时代码风格偏向面向对象;  再后面可能会深入STL和泛型,甚至模板元编程, 此时代码风格使用模板泛型; 最后有些人可能会觉得过度的关注面向对象的设计模式和模板的泛型设计, 会让人偏离对要解决的问题本身的关注, 最后他的风格又回到了原始的C或是刚毕业时带类的C的风格。

从上面可以看到,对于C++程序风格,我们很难定出一个比较统一的标准,但是我想我们可以根据我们要解决的问题不同而使用不同的风格。下面是我个人的一些看法:

(1)C++底层语言基础库(STL, Boost)以泛型为主导, 以高效和通用为设计原则, 这方面我想大家已经达成共识

(2)C++应用基础库和框架以面向对象和泛型为主导。基础框架一般对扩展性和性能都有一定要求,对于框架一般我们是大量实践经验的总结,所以我们基本上已经知道它的所有可变情况, 所以理论上我们可以进行精致的设计,然后通过模板参数的Traits和Policy来分离所有可能的情况,框架本身也有一定的复杂性,需要面向对象来封装和解耦, ATL是这方面作为COM组件开发基础库的成功例子。基础框架以高效,专用和扩展性为设计原则。

(3)C++应用层以面向对象为主导。应用层逻辑是多变的, 理论上你也可以采用模板参数的方式来应对变化, 但是应用层的变化非常复杂, 很多事不可预测的, 所以你不可能以模板参数的方式预测到所有可能的情况。另外C++现在还没有对泛型Concepts的描述机制, 导致模板代码比较难懂。在多变的应用层大量采用模板显然不是一个好的选择。 另外模板在应用层的大量使用也没有比较成熟的经验, 而面向对象和模式已经是非常成熟。应用层以低耦合,灵活应对变化为设计原则。

(4)C++模块(DLL)间的交互则以C方式API或是仿COM(Interface+Factory)为主导, 模块接口和交互以简洁和二进制兼容为设计原则。

总之, 我们应该灵活应用C++各种风格和范型的特点, 采用 ”多范型“ 程序设计的思路来解决问题, 而不是采用单一风格。

最后,回到我最初的公司核心代码, 该代码是用来解决某个特定问题, 显然与通用性和可扩展性关系都不大, 也就不需要所谓的模式和模板了, 实际上你越往操作系统底层, 你离这些抽象的东西就越远, 所以Linux之父才会给C++差评。


http://www.cppblog.com/weiym
posted @ 2014-10-14 09:45 Enic 阅读(325) | 评论 (0)编辑 收藏

SQL连接可以分为内连接、外连接、交叉连接。

 

数据库数据:

           

book表                                          stu表

 

1.内连接

1.1.等值连接:在连接条件中使用等于号(=)运算符比较被连接列的列值,其查询结果中列出被连接表中的所有列,包括其中的重复列。

1.2.不等值连接:在连接条件使用除等于运算符以外的其它比较运算符比较被连接的列的列值。这些运算符包括>、>=、<=、<、!>、!<和<>。

1.3.自然连接:在连接条件中使用等于(=)运算符比较被连接列的列值,但它使用选择列表指出查询结果集合中所包括的列,并删除连接表中的重复列。

内连接:内连接查询操作列出与连接条件匹配的数据行,它使用比较运算符比较被连接列的列值。

select * from book as a,stu as b where a.sutid = b.stuid  select * from book as a inner join stu as b on a.sutid = b.stuid

内连接可以使用上面两种方式,其中第二种方式的inner可以省略。

其连接结果如上图,是按照a.stuid = b.stuid进行连接。

 

2.外连接

2.1.左联接:是以左表为基准,将a.stuid = b.stuid的数据进行连接,然后将左表没有的对应项显示,右表的列为NULL

select * from book as a left join stu as b on a.sutid = b.stuid

2.2.右连接:是以右表为基准,将a.stuid = b.stuid的数据进行连接,然以将右表没有的对应项显示,左表的列为NULL

select * from book as a right join stu as b on a.sutid = b.stuid

2.3.全连接:完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。

select * from book as a full outer join stu as b on a.sutid = b.stuid

 

3.交叉连接

交叉连接:交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。

select * from book as a cross join stu as b order by a.id

posted @ 2014-10-05 11:09 Enic 阅读(143) | 评论 (0)编辑 收藏

     摘要: C++11与Unicode及使用标准库进行UTF-8、UTF-16、UCS2、UCS4/UTF-32编码转换作者: 破晓 日期: 2014年2月28日发表评论 (0)查看评论UnicodeUnicode是计算机领域的一项行业标准,它对世界上绝大部分的文字的进行整理和统一编码,Unicode的编码空间可以划分为17个平面(plane),每个平面包含2的16次方(655...  阅读全文
posted @ 2014-09-25 14:53 Enic 阅读(10912) | 评论 (0)编辑 收藏

仅列出标题
共22页: First 5 6 7 8 9 10 11 12 13 Last