??xml version="1.0" encoding="utf-8" standalone="yes"?>
出处QPConline
1. struct的巨大作?br /> 面对一个h的大型C/C++E序Ӟ只看其对struct的用情冉|们就可以对其~写者的~程l验q行评估。因Z个大型的C/C++E序Q势必要涉及一?甚至大量)q行数据l合的结构体Q这些结构体可以原本意义属于一个整体的数据l合在一赗从某种E度上来_会不会用structQ怎样用struct是区别一个开发h员是否具备丰富开发经历的标志?/font>
在网l协议、通信控制、嵌入式pȝ的C/C++~程中,我们l常要传送的不是单的字节(char型数l)Q而是多种数据l合h的一个整体,其表现Ş式是一个结构体?/font>
l验不的开发h员往往所有需要传送的内容依顺序保存在char型数l中Q通过指针偏移的方法传送网l报文等信息。这样做~程复杂Q易出错Q而且一旦控制方式及通信协议有所变化Q程序就要进行非常细致的修改?/font>
一个有l验的开发者则灉|q用l构体,举一个例子,假设|络或控制协议中需要传送三U报文,其格式分别ؓpacketA、packetB、packetCQ?/font>
struct structA
{
int a;
char b;
};
struct structB
{
char a;
short b;
};
struct structC
{
int a;
char b;
float c;
}
优秀的程序设计者这栯计传送的报文Q?/font>
struct CommuPacket
{
int iPacketType; //报文cd标志
union //每次传送的是三U报文中的一U,使用union
{
struct structA packetA;
struct structB packetB;
struct structC packetC;
}
};
在进行报文传送时Q直接传送struct CommuPacket一个整体?/font>
假设发送函数的原Ş如下Q?/font>
// pSendDataQ发送字节流的首地址QiLenQ要发送的长度
Send(char * pSendData, unsigned int iLen);
发送方可以直接q行如下调用发送struct CommuPacket的一个实例sendCommuPacketQ?br />Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
假设接收函数的原形如下:
// pRecvDataQ发送字节流的首地址QiLenQ要接收的长?br />//q回|实际接收到的字节?br />unsigned int Recv(char * pRecvData, unsigned int iLen)Q?br /> 接收方可以直接进行如下调用将接收到的数据保存在struct CommuPacket的一个实例recvCommuPacket中:
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );
接着判断报文cdq行相应处理Q?/font>
switch(recvCommuPacket. iPacketType)
{
case PACKET_A:
… ?//AcL文处?br /> break;
case PACKET_B:
… //BcL文处?br /> break;
case PACKET_C:
… ?//CcL文处?br /> break;
}
以上E序中最值得注意的是
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );
中的强制cd转换Q?char *)&sendCommuPacket?char *)&recvCommuPacketQ先取地址Q再转化为char型指针,q样可以直接利用处理字节流的函数?/font>
利用q种强制cd转化Q我们还可以方便E序的编写,例如要对sendCommuPacket所处内存初始化?Q可以这栯用标准库函数memset()Q?/font>
memset((char *)&sendCommuPacket,0, sizeof(CommuPacket));
2. struct的成员对?br /> Intel、微软等公司曄一道类似的面试题:
1. #include <iostream.h>
2. #pragma pack(8)
3. struct example1
4. {
5. short a;
6. long b;
7. };
8. struct example2
9. {
10. char c;
11. example1 struct1;
12. short e;
13. };
14. #pragma pack()
15. int main(int argc, char* argv[])
16. {
17. example2 struct2;
18. cout << sizeof(example1) << endl;
19. cout << sizeof(example2) << endl;
20. cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;
21. return 0;
22. }
问程序的输入l果是什么?
{案是:
8
16
4
不明白?q是不明白?下面一一道来Q?br />2.1 自然对界
struct是一U复合数据类型,其构成元素既可以是基本数据类型(如int、long、float{)的变量,也可以是一些复合数据类型(如array、struct、union{)的数据单元。对于结构体Q编译器会自动进行成员变量的寚wQ以提高q算效率。缺省情况下Q编译器为结构体的每个成员按其自然对界(natural alignmentQ条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,W一个成员的地址和整个结构的地址相同?br /> 自然对界(natural alignment)即默认对齐方式,是指按结构体的成员中size最大的成员寚w?br /> 例如Q?br />struct naturalalign
{
char a;
short b;
char c;
};
在上q结构体中,size最大的是shortQ其长度?字节Q因而结构体中的char成员a、c都以2为单位对齐,sizeof(naturalalign)的结果等?Q?br /> 如果改ؓQ?br />struct naturalalign
{
char a;
int b;
char c;
};
其结果显然ؓ12?/font>
2.2指定对界
一般地Q可以通过下面的方法来改变~省的对界条Ӟ
· 使用伪指?pragma pack (n)Q编译器按照n个字节对齐;
· 使用伪指?pragma pack ()Q取消自定义字节寚w方式?br /> 注意Q如?pragma pack (n)中指定的n大于l构体中最大成员的sizeQ则其不起作用,l构体仍然按照size最大的成员q行对界?br /> 例如Q?br />#pragma pack (n)
struct naturalalign
{
char a;
int b;
char c;
};
#pragma pack ()
当n???6Ӟ其对齐方式均一Psizeof(naturalalign)的结果都{于12。而当n?Ӟ其发挥了作用Q得sizeof(naturalalign)的结果ؓ8?br /> 在VC++ 6.0~译器中Q我们可以指定其对界方式Q其操作方式Zơ选择projetct > setting > C/C++菜单Q在struct member alignment中指定你要的对界方式?br /> 另外Q通过__attribute((aligned (n)))也可以让所作用的结构体成员寚w在n字节边界上,但是它较被使用Q因而不作详l讲解?/font>
2.3 面试题的解答
xQ我们可以对Intel、微软的面试题进行全面的解答?br /> E序中第2?pragma pack (8)虽然指定了对界ؓ8Q但是由于struct example1中的成员最大size?Qlong变量size?Q,故struct example1仍然?字节对界Qstruct example1的size?Q即W?8行的输出l果Q?br /> struct example2中包含了struct example1Q其本n包含的简单数据成员的最大size?Qshort变量eQ,但是因ؓ其包含了struct example1Q而struct example1中的最大成员size?Qstruct example2也应?对界Q?pragma pack (8)中指定的对界对struct example2也不起作用,?9行的输出l果?6Q?br /> ׃struct example2中的成员?为单位对界,故其char变量c后应补充3个空Q其后才是成员struct1的内存空_20行的输出l果??br />
二、前期准?
首先我们建立一张名为userinfo的表Q包含三个字D?id,username,old,photo,其中photo是一个可以存储二q制数据的字Dc?
2.1 在SQL SERVER中我们可以在Query Analyzer中直接输入如下语句创建:
CREATE TABLE [dbo].[userphoto] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[username] [varchar] (50) NULL ,
[old] [int] NULL ,
[photo] [image] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
其中photo我们定义为imagecd的字Dc?
2.2 在ACCESS中创建的Ҏ如下Q?
建立一张新表包括id,username,old,photo四个字段Q然后打开表,选视图菜单中设计视图Q将id讄动编L递增长整型,username为文本,old为数字,photo为OLE对象?
在我们的CZ工程中已l包含了一个徏立好的ACCESS2000的库Q你可以直接拿来使用?
三、具体步?
3.1 BLOB数据的保?
BLOBcd的数据无法用普通的方式q行存储Q我们需要用AppendChunk函数QAppendChunk包含在Field对象中,原型如下Q?
HRESULT AppendChunk (const _variant_t & Data );
从函数原型中可以看到关键的问题是我们需把二q制数据赋值给VARIANTcd的变量,下面我们l出具体的代码ƈ作简单的分析:
///假设m_pBMPBuffer指针指向一块长度ؓm_nFileLen的二q制数据,q且已经成功打开了记录集对象m_pRecordset///
char *pBuf = m_pBMPBuffer ;
VARIANT varBLOB;
SAFEARRAY *psa;
SAFEARRAYBOUND rgsabound[1];
m_pRecordset->AddNew(); ///d新记?
m_pRecordset->PutCollect("username",_variant_t("李")); ///为新记录填充username字段
m_pRecordset->PutCollect("old",_variant_t((long)28); ///填充old字段
if(pBuf){
rgsabound[0].lLbound = 0;
rgsabound[0].cElements = m_nFileLen;
psa = SafeArrayCreate(VT_UI1, 1, rgsabound); ///创徏SAFEARRAY对象
for (long i = 0; i < (long)m_nFileLen; i++)
SafeArrayPutElement (psa, &i, pBuf++); ///pBuf指向的二q制数据保存到SAFEARRAY对象psa?
varBLOB.vt = VT_ARRAY | VT_UI1;///varBLOB的类型设|ؓBYTEcd的数l?
varBLOB.parray = psa; ///为varBLOB变量赋?
m_pRecordset->GetFields()->GetItem("photo")->AppendChunk(varBLOB); ///加入BLOBcd的数?
}
m_pRecordset->Update(); ///保存我们的数据到库中
x我们的数据已l成功地保存C数据库中,接下来我们所要做的工作便是将该数据提取出?让我们l吧!
3.2 BLOB数据的读?
对应于保存数据时我们所使用的AppendChunk函数Q读取数据应该用GetChunk函数,GetChunk的原型如?
_variant_t GetChunk (long Length );
l出数据的长度后GetChunk返回包含数据的VARIANTcd变量,然后我们可以利用SafeArrayAccessData函数得到VARIANT变量中指向数据的char *cd的指?以方便我们的处理Q具体代码如下:
long lDataSize = m_pRecordset->GetFields()->GetItem("photo")->ActualSize;
///得到数据的长?
if(lDataSize > 0)
{
_variant_t varBLOB;
varBLOB = m_pRecordset->GetFields()->GetItem("photo")->GetChunk(lDataSize);
if(varBLOB.vt == (VT_ARRAY | VT_UI1)) ///判断数据cd是否正确
{
char *pBuf = NULL;
SafeArrayAccessData(varBLOB.parray,(void **)&pBuf);
///得到指向数据的指?/*****在这里我们可以对pBuf中的数据q行处理*****/
SafeArrayUnaccessData (varBLOB.parray);
}
}
以上我们成功实现了BLOB数据在数据库中的存取Qؓ了让大家有现成的例子可以参考,本文提供了示例工E,在示例工E中我们在数据库中保存图像数据,q可以对q些囑փq行览、修改,该例子还涉及到如何用char *指向的BMP文g数据创徏BITMAP对象然后昄出来?br />
long
i
= 0;
VARIANT
va
= {0};
va
.
vt
=
VT_BSTR
;
SAFEARRAYBOUND
bounds
[1] = {0};
bounds
[0].
cElements
= 5;
SAFEARRAY
*
psa
=
SafeArrayCreate
(
VT_VARIANT
, 1,
bounds
);
|
l
存入元素
for
(
i
= 0;
i
< 5;
i
++)
{
va
.
bstrVal
=
SysAllocString
(L
"test"
);
SafeArrayPutElement
(
psa
, &
i
, &
va
);
}
|
l
获取元素
for
(
i
= 0;
i
< 5;
i
++)
{
va
.
bstrVal
=
SysAllocString
(L
"test"
);
SafeArrayGetElement
(
psa
, &
i
, &
va
);
SysFreeString
(
va
.
bstrVal
);
}
|
l
销?/span>
SafeArrayDestroy
(
psa
);
|
l
生成
VARIANT
变量
VARIANT
vsa
= {0};
vsa
.
vt
=
VT_ARRAY
|
VT_BSTR
; vsa . parray = psa ; |
首先误位看以下一D“危险”的C++代码Q?/p>
void function( void ) |
之所以说其危险,是因是一D完全合乎语法的代码Q编译的时候完得一炚w误也不会有,然而当q行到strcpy一句的时候,问题׃出现Q因为在q之前,str的空间已l被delete掉了Q所以strcpy当然不会成功。对于这U类似的情况Q在林锐博士的书中有q介l,U其为“野指针”?/p>
那么Q诸位有没有见过安全的“野指针”呢Q下面请看我的一DC++E序Q灵感来自CSDN上的一ơ讨论。在此,我只需要C++的“类”,C++的其余一概不需要,因此我没有用Q何的C++标准库,q输出都是用printf完成的?/p>
#include <stdio.h> |
OKQ程序到此ؓ止,怽可以~译q行一下看看结果如何。你也许会惊异地发现Q没有Q何的出错信息Q屏q上竟然乖乖地出Cq么一行字W串Q?/p>
This is a test function.
奇怪吗Q不要急,q有更奇怪的呢,你可以把dC加上一句更不可理喻的:
((CTestClass*)NULL)->Function(); |
q仍然没有问题!Q?/p>
我这q有呢,哈哈。现在你在主函数中这么写Q倘说上一句不可理喻,那么以下可以叫做无法无天了:
int i = 888; |
你看C什么?是的Q“This is a test function.”如U而至Q没有Q何的错误?/p>
你也许要问ؓ什么,但是在我解答你之前,请你在主函数中加入如下代码:
printf( "%d, %d", sizeof( CTestClass ), sizeof( int ) ); |
q时你就会看到真怺Q输出结果是——得到的两个十进制数相等。对Q由sizeof得到的CTestClass的大其实就是它的成员m_nInteger的大。亦x_对于CTestClass的一个实例化的对象(设ؓaQ而言Q只有a.m_nInteger是属于aq个对象的,而a.Function()却是属于CTestClassq个cȝ。所以以上看似危险的操作其实都是可行且无误的?/p>
现在你明白ؓ什么我的“野指针”是安全的了Q那么以下我所列出的,是在什么情况下Q我的“野指针”不安全Q?/p>
以上的两U情况,目的是野指针用属于自q东西D不安全,比如W一U情况中操作本n的m_nIntegerQ第二种情况中变函数的Function成ؓ了属于对象的函数Q这一点可以从sizeof看出来)?/p>
其实Q安全的野指针在实际的程序设计中是几乎毫无用处的。我写这一文章,意图q不是像孔乙׃样去琢磨回字有几U写法,而是想通过q个例子向怽写明白C++的对象实例化本质Q希望大家不但要明白what和howQ更要明白why。李马二雉三年二月二十日作于自宅?/p>
关于成员函数CTestClass::Function的补充说?/b>
q个函数是一个普通的成员函数Q它在编译器的处理下Q会成ؓcM如下的代码:
void Function( const CTestClass * this ) // ?/font> |
那么p->Function();一句将被编译器解释为:
Function( p ); |
q就是说Q普通的成员函数必须l由一个对象来调用Q经由this指针ȀzZQ。那么由上例的delete之后Qp指针会指向一个无效的地址Q然而p本n是一个有效的变量Q因此编译能够通过。ƈ且在~译通过之后Q由于CTestClass::Function的函C内ƈ未对q个传入的this指针q行M的操作,所以在q里Q“野指针”便成了一个看似安全的东西?/p>
然而若q样改写CTestClass::FunctionQ?/p>
void CTestClass::Function( void ) |
那么它将会被~译器解释ؓQ?/p>
void Function( const CTestClass * this ) |
你看CQ在p->Function();的时候,pȝ会试在传入的q个无效地址中寻找m_nInteger成员q将其赋gؓ0Q剩下的我不用说了——非法操作出C?/p>
至于virtual虚函敎ͼ如果在类定义之中CTestClass声明函数Q?/p>
class CTestClass |
那么C++在构建CTestClasscȝ对象模型Ӟ会Z分配一个虚函数表vptrQ可以从sizeof看出来)。vptr是一个指针,它指向一个函数指针的数组Q数l中的成员即是在CTestClass中声明的所有虚函数。在调用虚函数的时候,必须l由q个vptrQ这也就是ؓ什么虚函数较之普通成员函数要消耗一些成本的~故。以本例而言Qp->Function();一句将被编译器解释为:
(*p->vptr[1])( p ); // 调用vptr表中索引号ؓ1的函敎ͼ即FunctionQ③ |
上面的代码已l说明了Q如果p指向一个无效的地址Q那么必然会有非法操作?/p>
备注Q?/p>
①关于函数的命名Q我采用了原名而没有变化。事实上~译器ؓ了避免函数重载造成的重名情况,会对函数的名字进行处理,使之成ؓ独一无二的名U?br />②将成员函数声明为staticQ可以成员函数不经由this指针便可调用?br />③vptr表中Q烦引号0为类的type_info?/p>
二、自动配|数据源
很多的程序都要用到数据库l合的操作,q里举例ACCESSQ因为ACCESS在中型VCpȝ开发中是最常用到的Q如果程序的ULQ如果对于很初的用h_你还需要他到配|面板中q行数据源配|的话,那就有点说不q去了。?
#include <odbcinst.h>
//配置数据源,数据库在应用E序目录?q里比如数据库文件名?**.mdbQ程序运行时候可以将数据库文件拷贝到E序目录下面。?
CString sPath;
GetModuleFileName(NULL,sPath.GetBufferSetLength (MAX_PATH+1),MAX_PATH);
sPath.ReleaseBuffer();
int nPos;
nPos=sPath.ReverseFind(’\\?;
sPath=sPath.Left(nPos);
CString lpszFileName = sPath + "\\***.mdb";//q里修改成你的数据库文g名称
CFileFind fFind;
if(!fFind.FindFile(lpszFileName))
{
::AfxMessageBox("没有扑ֈ数据?);
exit(0);
}
CString szDesc;
szDesc.Format( "DSN=****;Description=****;DESCRIPTION=The DataBase For ***;FIL=MicrosoftAccess;DEFAULTDIR=%s;DBQ=%s;" ,sPath,lpszFileName);//q里***号可以添加成你的描述
//d数据源?
if(!::SQLConfigDataSource(NULL,ODBC_ADD_DSN, "Microsoft Access Driver (*.mdb)",(LPCSTR)szDesc))
{
::AfxMessageBox("32位ODBC数据源配|错?");
exit(0);
}
三、设|显C模式:
很多的程序的UL的运行环境是改变的。有可能你的原来开发环境是1024X768Q但是到了那些显C器大于17的时候(分L率超q你的开发时的分辨率ӞQ程序的昄可能׃好看了。?
DEVMODE lpDevMode;
lpDevMode.dmPelsHeight=768;//Y方向象素点?
lpDevMode.dmPelsWidth=1024;//X方向象素点?
lpDevMode.dmDisplayFrequency=85;//屏幕h率?
lpDevMode.dmFields=DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
ChangeDisplaySettings(&lpDevMode,0);
四、在你的E序中间加蝲其他应用E序Q?
你的E序除了调用到各个模块进行协同工作外QDLLQ,q有可能调用不是同一个开发环境下的应用程序,比如VC环境下调用DELPHIQVB开发的执行E序Q你可以用C面的ҎQ将调用的应用程序拷贝程序目录中Q:
CString sPath;
GetModuleFileName(NULL,sPath.GetBufferSetLength (MAX_PATH+1),MAX_PATH);
sPath.ReleaseBuffer();
int nPos;
nPos=sPath.ReverseFind(’\\?;
sPath=sPath.Left(nPos);
CString lpszFileName = sPath + "\\***.exe";//q里修改成你的调用应用程序的文g名称
CFileFind fFind;
if(!fFind.FindFile(lpszFileName))
{
::AfxMessageBox("没有扑ֈ调用的应用程序!");
return FALSE;
}
else
ShellExecute(NULL,NULL,_T("***.exe"),NULL,sPath,NULL);
五、结束进E:
在你的程序中l束别的E序q程Q采用的Ҏ是进行进E列举,然后采用查找的方法进行:
#include "TLHELP32.H"
DWORD ProcessID[50];
CString kkk[50];
HANDLE SnapShot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
PROCESSENTRY32* info=new PROCESSENTRY32;
PROCESSENTRY32 ProcessInfo;//声明q程信息变量
ProcessInfo.dwSize=sizeof(ProcessInfo);//讄ProcessInfo的大?
//q回pȝ中第一个进E的信息
BOOL Status=Process32First(SnapShot,&ProcessInfo);
int m_nProcess=0;
while(Status)
{
CString s,str1,str2;
s.Format("%d",ProcessInfo.cntThreads);
str1.Format("%s",ProcessInfo.szExeFile);
str1=ProcessInfo.szExeFile;
kkk[m_nProcess]=ProcessInfo.szExeFile;
ProcessID[m_nProcess]=ProcessInfo.th32ProcessID;
if(str1=="***.exe")//***.exe是你要l束的进E的名称
{
HANDLE ProcessHandle;
ProcessHandle=OpenProcess (PROCESS_ALL_ACCESS,FALSE,ProcessID[m_nProcess]);
TerminateProcess(ProcessHandle,0);
}
Status=Process32Next(SnapShot,&ProcessInfo);
m_nProcess++;
}
#define WM_DEBUG WM_USER + 1999
2. 在窗口头文g中添?/p>
class CStreamServerDlg : public CDialog
{
// Generated message map functions
//{{AFX_MSG(CStreamServerDlg)
...
//}}AFX_MSG
afx_msg void OnDebug(WPARAM wParam, LPARAM lParam);
...
}
3. 在窗口的cpp文g中添?/p>
BEGIN_MESSAGE_MAP(CStreamServerDlg, CDialog)
...
ON_MESSAGE(WM_DEBUG, OnDebug)
END_MESSAGE_MAP()
LRESULT CStreamServerDlg::OnDebug(WPARAM wParam, LPARAM lParam)
{
}
4. 其他地方可以发送消?/p>
pWnd->PostMessage(WM_DEBUG, (WPARAM)p, 0) )
说明Q?br />
#define ON_MESSAGE(message, memberFxn)
{ message, 0, 0, 0, AfxSig_lwl,
(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&memberFxn },