- 单一职责原则
- 变量初始化
- 字符串操作
- 需要结对出现的代码,应在第一时间写好并对应匹配,避免漏掉或交叉
- 所有按引用传递的不修改的参数必须加上const
- 传进参数加IN 传出参数加OUT
- 浮点数比较
- 指针和句柄使用完释放后请设置成NULL
- 不要吝惜大括号
- 保持函数返回值类型一致
- 不要混用 delete 与 delete[]
- 不要使用已释放的内存
- 迭代中的删除操作
更多C++风格指南可以参考:C++ 风格指南 - 内容目录 — Google 开源项目风格指南
单一职责原则
无论是变量、函数、类还是模块,尽量遵循单一职责原则,能保持逻辑的线性简单,不会把各种问题都搅在一起。
变量初始化
所幸的是,C++高级版本可以在类的头文件中可以直接对变量声明时直接初始化了。
字符串操作
优先推荐CString,其次string,最后才是字符串操作API。
_tcscpy_s
_tcscpy
vswprintf_s
_vstprintf_s
_vsntprintf_s
_stprintf_s
_stprintf
_tprintf
_strupr_s
strncpy_s
_strnicmp
wcsncpy_s
_vsctprintf
- 如果函数需要返回字符串类型,则返回CString或string,不要返回char *,除非是DLL接口或其他特殊情况。
- 使用API诸如lstrcpy,strcat,sprintf等字符串操作函数,很容易在逆向的时候被查看到符号,而且也很容易通过HOOK函数打印出参数内容,而使用字符串类相对就困难一些了。
在Windows开发中强烈推荐使用CString,使用该类只需要包含头文件atlstr.h即可,该类不是MFC类(以前是,后来剥离出来了)。再不济可以使用 std::string ,为了程序兼容Unicode和多字节字符集,可以预定义一个宏:
#ifndef mystring
#ifdef _UNICODE
#define mystring wstring
#else
#define mystring string
#endif
#endif
然后在程序中使用mystring即可。
但是std::string有一个缺点是没有一个好用的字符串格式化函数,因此某些时候仍然需要调用:_stprintf、_stprintf_s、vsprintf_s等相关函数,然而CString已经有封装好的函数可以使用,就是Format函数。
使用CString的另一个好处是,它是兼容Unicode和多字节字符集的,在Unicode字符集模式下,它是CStringW,而在多字节模式下它是CstringA。更加强大的一个功能是,可以为CString类型的字符变量赋值多字节字符串或Unicode字符串,也即你可以这样做:
CString strTemp;
CStringA strTextA;
std:tring s = “123456”;
strTextA = s.c_str();
strTemp += strTextA;
但是如果你是从事Android NDK项目的开发,找不到CString,退而求其次,可以使用std::string。以下列出了常见的字符串操作函数,记住:不要直接使用字符串操作API,而是优先使用封装好的成员函数。
需要结对出现的代码,应在第一时间写好并对应匹配,避免漏掉或交叉
曾经遇到过一例 #pramga pack 的使用因为漏掉另一个导致对三方库的字节对齐造成影响引起的崩溃。
new/delete等堆操作,必须成对出现。尽量遵从谁分配谁释放的原则。
所有按引用传递的不修改的参数必须加上const
如果希望某个变量或者参数不被修改,加上关键字const修饰。
void Foo (const string & in, string * out);
事实上这在Google Code是一个硬性约定:输入参数是值参或const引用,输出参数为指针.输入参数可以是const指针,但决不能是非const的引用参数,除非用于交换,比如swap()。 有时候,在输入形参中用const T*指针比const T&更明智。比如:
- 您会传null指针。
- 函数要把指针或对地址的引用贼值给输入形参。
总之大多时候输入形参往往是 const T&.若用const T说明输入另有处理。所以若您要用const T ,则应有理有据,否则会害得读者误解。
传进参数加IN 传出参数加OUT
本身IN和OUT没有任何意义,只是让使用者很容易分清楚参数是传进还是传出,还是传进且传出。
浮点数比较
不可将浮点变量用“==”或“!=”与任何数字比较。无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避免将浮点变量用“ ==”或“!=”与数字比较,应该设法转化成“ >=”或“ <=”形式。 假设浮点变量名字为x,应当将
if (x == 0.0)//隐含错误的比较
修改为
const float EPSINON = 0.00001;
if ((x >= -EPSINON) && (x <= EPSINON))
其中EPSINON是运行的误差(即精度)。
指针和句柄使用完释放后请设置成NULL
hDC = ::GetDC(NULL);
hCompatDC = CreateCompatibleDC(hDC);
hBitmap = CreateCompatibleBitmap(hDC, nWidth, nHeight);
SelectObject(hCompatDC, hBitmap);
bSuccess = BitBlt(hCompatDC, 0, 0, nWidth, nHeight, hDC, nLeft, nTop, SRCCOPY | CAPTUREBLT);
DeleteDC(hCompatDC);
ReleaseDC(NULL, hDC);
……
if (hBitmap != NULL) {
CImage image;
image.Attach(hBitmap);
dwError = CreateStreamOnHGlobal(NULL, TRUE, ppStream);
if (dwError == S_OK && *ppStream != NULL) {
dwError = image.Save(*ppStream, Gdiplus::ImageFormatJPEG);
} else {
dwError = 5;
}
::DeleteObject(hBitmap);
}
不要吝惜大括号
曾经遇到过一例:
if (dwUIDLen > 0)
memcpy(pNewBuf + dwOffset, dstBuf, dwUIDLen); dwOffset += dwUIDLen;
保持函数返回值类型一致
如下,返回值类型与函数期望的返回值类型不一致:
// #define E_INVALIDARG _HRESULT_TYPEDEF_(0x80070057L)
BOOL xxx::yyy(IN LPBYTE pVerifyData, IN DWORD dwSize) {
BOOL bRet = TRUE;
DWORD dwOffset = 0;
DWORD dwBytesRead = 0;
DWORD dwBytesWrite = 0;
if (!pVerifyData) {
return E_INVALIDARG;
}
……
不要混用 delete 与 delete[]
常见,风险高。delete与delete[]混用的案例太多,如果本该使用delete[]的地方使用了delete可能不会造成大的风险,但是反过来如果本该使用delete的地方使用了delete[]就会引发灾难性的故障,而且往往崩溃堆栈是混乱的,非常难以排查。
曾经xxx遇到一个BUG,由于堆栈混乱无法定位,还好能够重现,于是本地通过二分法删减代码的方式不断缩小范围,才找到的原因,就是本该使用delete的地方使用了delete[]。现在想想如果用PVS工具应该很容易扫出来吧。
不要使用已释放的内存
内存释放后继续使用,在debug阶段可能不会出现什么问题,有时候在release阶段也不会暴露,但是BUG隐患一直存在,时间久了往往就不容易发现,然后又会在某一天突然显现,而且会导致堆栈异常,无法定位到准确的出错位置,非常难以排查。
曾经YYY的SO代码就遇到过这个问题,但是这个case只有在集成了我们的保护代码后才会显现,不集成我们的保护代码该case不会出现,一度被认为是我们的代码问题,后来我们的同事通过IDA分析到的,要求对YYY乐SO代码进行源码review才得以验证。
出现该问题的原因:猜测可能是变量复用导致,其实应该遵循单一职责原则。
迭代中的删除操作
错误的做法:
for (vector<tagUserInfo>::iterator i = m_vtUser.begin(); i != m_vtUser.end(); ++i) {
CString strFilePath = testFunc(i->dwProcessId);
if (strFilePath.CompareNoCase(i->strProcessFilePath) != 0) {
i = m_vtUser.erase(i);
}
}
正确的做法:
for (vector<tagUserInfo>::iterator i = m_vtUser.begin(); i != m_vtUser.end(); ) {
CString strFilePath = testFunc(i->dwProcessId);
if (strFilePath.CompareNoCase(i->strProcessFilePath) != 0) {
i = m_vtUser.erase(i);
} else {
++i;
}
}
文档信息
- 本文作者:zhupite
- 本文链接:https://zhupite.com/program/C++%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83%E5%8F%8A%E5%BB%BA%E8%AE%AE.html
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)