C++总结

本文是C++使用过程中的一些总结,时间大约15年左右

对齐:

-- 对于struct结构体,每个平台都会进行默认的对齐,4字节或者8字节,对齐方式虽然增加了存储空间,却能加快运行速度。
-- 但不同平台的对齐不同,在进行网络传输时,需要将对齐取消(DISALIGN用于取消)。这涉及到一种结构体中生命结构体,对齐的问题。

字节序问题:

-- 即大小端问题,在大端模式下,高字节存放在低地址下,低字节存放在高地址下。如0x1122,存放在0x0010地址上,
-- 若0x0010存放的是11而0x0011存放22,则是大端,反之为小端。涉及到大小端之间的测试即转化程序:

测试程序

short int x=0x1122;
char x0,x1;
x0 = ((char *)&x)[0];
x1 = ((char *)&x)[1];

若x0为11则为大端模式。

转换程序

-- 以转化字节的中间为轴,进行调换。
networktohost(void* Data, Juint16 DataLen)		//输入需要转换数据长度
{
// 只在 Sun Sparc 和 IBM PowerPC 平台上需要转换
//pengxiaoyan modify
//#if defined(__sparc) || defined(AIX)
#if defined(IES_BIG_ENDIAN)
//end modify
	Juint8 tmp;
	Juint8 *tmpData = (Juint8*)Data;

	for(int i=0; i<DataLen/2; i++)
	{
		tmp = tmpData[i];
		tmpData[i] = tmpData[DataLen-i-1];
		tmpData[DataLen-i-1] = tmp;
	}
#endif
}

unix 下问题

sun平台struct赋值问题

-- soloris下,struct结构体的赋值不能通过"="直接赋值,需要通过memcpy(&目的, &源, sizeof(目的));的形式来赋值,典型是STimeInfo结构体的赋值

IBM平台下问题

-- 1、对于map<Juint32, vector<STimeInfo>> 这种结构,对于IBM下对于">>"会认为是一个字符,存在问题,需要加空格,改为"> >"来解决。
-- 2、对于动态分配内存时,new的数组值必须为const形式。如:

 void newMemory(const char *pBuf, Juint16 nSize)
 {
   pBuf = new char[nSize];
 }

对于上述形式,IBM下会报错。

vector下find_if的使用:

-- find_if(vec.begin(), vec.end(), vecfinder(wDevID));
-- 其中vecfinder为一个函数类,实现如下:

class vecfinder
{
public:
  vecfinder(DATA_TYPE wDevID){m_devID = wDevID;}
  bool operate()(vec::data_value &data)
  {
  if(data.wDataValue == m_devID)
    return true;
  else
    return false;
  }

  private:
  int m_devID;
}

linux下map与set的erase问题

-- linux下map/set不用使用 itMap = MyMap.erase(itMap)的形式,其下map不返回iterater。
-- 网上摘抄:通过测试在不管在linux还是Windows平台下,vector,list都有方法iterator erase( iterator _Where ); 但是在Linux下map中没有方法iterator erase( iterator _Where ); 所以上述代码在Linux平台下第26行必须写成MyMap.erase(Itor++),而不能写能Itor = MyMap.erase(Itor). 在Windows下二种方法都可以。

模板

Template <typename T>
Int templateStudy(map<string,T>  &tmp)		//这种情况是不被允许的
{
	Map<string,T> tmp;				//可以
}

//这种情况在接受参数是不好使,而在函数内部进行数据的定义可以

Template <typename TMap>
Int templateStudy(TMap  &tmp)
{
	TMap::iterator it= tmp.begin();		//iterator是不被允许的。
	String s=TMap::value_type.first();	//同样value_type也是不被允许的。
}

感觉,模板在处理map、vector等数据结构上不太方便。对于泛类型,在实例化之前,没有办法去调用其内部的成员,如iterator、value_type等成员变量。

模板类所在的头文件中,开始时存在几个非模板的全局函数,连接时,出现这几个全局函数连接的错误提示。这几个全局函数是idatabrtdbmanager为idaloadtab而准备的。而在idatabshow中也连接了他们,所以提示重复。将这几个函数另外放置,只为idaloadtab准备使用,问题解决。

map 排序解析

map是用来存放<key, value>键值对的数据结构,可以很方便快速的根据key查到相应的value。假如存储学生和其成绩(假定不存在重名,当然可以对重名加以区分),我们用map来进行存储就是个不错的选择。 我们这样定义,map<string, int>,其中学生姓名用string类型,作为Key;该学生的成绩用int类型,作为value。这样一来,我们可以根据学生姓名快速的查找到他的成绩。
但是,我们除了希望能够查询某个学生的成绩,或许还想看看整体的情况。我们想把所有同学和他相应的成绩都输出来,并且按照我们想要的顺序进行输出:比如按照学生姓名的顺序进行输出,或者按照学生成绩的高低进行输出。换句话说,我们希望能够对map进行按Key排序或按Value排序,然后按序输出其键值对的内容。

C++ STL中Map的按Key排序

   其实,为了实现快速查找,map内部本身就是按序存储的(比如红黑树)。在我们插入`<key, value>`键值对时,就会按照key的大小顺序进行存储。这也是作为key的类型必须能够进行<运算比较的原因。现在我们用string类型作为key,因此,我们的存储就是按学生姓名的字典排序储存的。
#include<map>  
#include<string>  
#include<iostream>  
using namespace std;  
  
typedef pair<string, int> PAIR;  
  
ostream& operator<<(ostream& out, const PAIR& p) {  
  return out << p.first << "\t" << p.second;  
}  
  
int main() {  
  map<string, int> name_score_map;  
  name_score_map["LiMin"] = 90;   
  name_score_map["ZiLinMi"] = 79;   
  name_score_map["BoB"] = 92;   
  name_score_map.insert(make_pair("Bing",99));  
  name_score_map.insert(make_pair("Albert",86));  
  for (map<string, int>::iterator iter = name_score_map.begin();  
        iter != name_score_map.end();  
        ++iter) {  
    cout << *iter << endl;  
  }  
  return 0;  
}

Albert 86
Bing 99
BoB 92
LiMin 90
ZiLinMi 79

大家都知道map是stl里面的一个模板类,现在我们来看下map的定义:

template < class Key, class T, class Compare = less<Key>,  
    class Allocator = allocator<pair<const Key,T> > > class map; 

它有四个参数,其中我们比较熟悉的有两个: Key 和 Value。第四个是 Allocator,用来定义存储分配模型的,此处我们不作介绍。
现在我们重点看下第三个参数: class Compare = less
这也是一个class类型的,而且提供了默认值 less。 less是stl里面的一个函数对象,那么什么是函数对象呢?
所谓的函数对象:即调用操作符的类,其对象常称为函数对象(function object),它们是行为类似函数的对象。表现出一个函数的特征,就是通过“对象名+(参数列表)”的方式使用一个 类,其实质是对operator()操作符的重载。
现在我们来看一下less的实现:

template <class T> struct less : binary_function <T,T,bool> {  
  bool operator() (const T& x, const T& y) const  
    {return x<y;}  
};  

它是一个带模板的struct,里面仅仅对()运算符进行了重载,实现很简单,但用起来很方便,这就是函数对象的优点所在。stl中还为四则运算等常见运算定义了这样的函数对象,与less相对的还有greater:

template <class T> struct greater : binary_function <T,T,bool> {  
  bool operator() (const T& x, const T& y) const  
    {return x>y;}  
}; 

map这里指定less作为其默认比较函数(对象),所以我们通常如果不自己指定Compare,map中键值对就会按照Key的less顺序进行组织存储,因此我们就看到了上面代码输出结果是按照学生姓名的字典顺序输出的,即string的less序列。
我们可以在定义map的时候,指定它的第三个参数Compare,比如我们把默认的less指定为greater:
【参考代码】

#include<map>  
#include<string>  
#include<iostream>  
using namespace std;  
  
typedef pair<string, int> PAIR;  
  
ostream& operator<<(ostream& out, const PAIR& p) {  
  return out << p.first << "\t" << p.second;  
}  
  
int main() {  
  map<string, int, greater<string> > name_score_map;  
  name_score_map["LiMin"] = 90;   
  name_score_map["ZiLinMi"] = 79;   
  name_score_map["BoB"] = 92;   
  name_score_map.insert(make_pair("Bing",99));  
  name_score_map.insert(make_pair("Albert",86));  
  for (map<string, int>::iterator iter = name_score_map.begin();  
       iter != name_score_map.end();  
       ++iter) {  
    cout << *iter << endl;  
  }  
  return 0;  
}  

【运行结果】
ZiLinMi 79
LiMin 90
BoB 92
Bing 99
Albert 86

现在知道如何为map指定Compare类了,如果我们想自己写一个compare的类,让map按照我们想要的顺序来存储,比如,按照学生姓名的长短排序进行存储,那该怎么做呢?
其实很简单,只要我们自己写一个函数对象,实现想要的逻辑,定义map的时候把Compare指定为我们自己编写的这个就ok啦。

struct CmpByKeyLength {  
  bool operator()(const string& k1, const string& k2) {  
    return k1.length() < k2.length();  
  }  
};  

是不是很简单!这里我们不用把它定义为模板,直接指定它的参数为string类型就可以了。
【参考代码】

int main() {  
  map<string, int, CmpByKeyLength> name_score_map;  
  name_score_map["LiMin"] = 90;   
  name_score_map["ZiLinMi"] = 79;   
  name_score_map["BoB"] = 92;   
  name_score_map.insert(make_pair("Bing",99));  
  name_score_map.insert(make_pair("Albert",86));  
  for (map<string, int>::iterator iter = name_score_map.begin();  
       iter != name_score_map.end();  
       ++iter) {  
    cout << *iter << endl;  
  }  
  return 0;  
}  

【运行结果】
BoB 92
Bing 99
LiMin 90
Albert 86
ZiLinMi 79

C++ STL中Map的按Value排序

    在第一部分中,我们借助map提供的参数接口,为它指定相应Compare类,就可以实现对map按Key排序,是在创建map并不断的向其中添加元素的过程中就会完成排序。

现在我们想要从map中得到学生按成绩的从低到高的次序输出,该如何实现呢?换句话说,该如何实现Map的按Value排序呢?
第一反应是利用stl中提供的sort算法实现,这个想法是好的,不幸的是,sort算法有个限制,利用sort算法只能对序列容器进行排序,就是线性的(如vector,list,deque)。map也是一个集合容器,它里面存储的元素是pair,但是它不是线性存储的(前面提过,像红黑树),所以利用sort不能直接和map结合进行排序。
虽然不能直接用sort对map进行排序,那么我们可不可以迂回一下,把map中的元素放到序列容器(如vector)中,然后再对这些元素进行排序呢?这个想法看似是可行的。要对序列容器中的元素进行排序,也有个必要条件:就是容器中的元素必须是可比较的,也就是实现了<操作的。那么我们现在就来看下map中的元素满足这个条件么?
我们知道map中的元素类型为pair,具体定义如下:

template <class T1, class T2> struct pair  
{  
  typedef T1 first_type;  
  typedef T2 second_type;  
  
  T1 first;  
  T2 second;  
  pair() : first(T1()), second(T2()) {}  
  pair(const T1& x, const T2& y) : first(x), second(y) {}  
  template <class U, class V>  
    pair (const pair<U,V> &p) : first(p.first), second(p.second) { }  
}  

pair也是一个模板类,这样就实现了良好的通用性。它仅有两个数据成员first 和 second,即 key 和 value,而且
头文件中,还为pair重载了 < 运算符, 具体实现如下:

template<class _T1, class _T2>  
  inline bool  
  operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)  
  { return __x.first < __y.first  
           || (!(__y.first < __x.first) && __x.second < __y.second); } 

重点看下其实现:

  1. __x.first < __y.first || (!(__y.first < __x.first) && __x.second < __y.second)
    这个less在两种情况下返回true,第一种情况:__x.first < __y.first 这个好理解,就是比较key,如果__x的key 小于 __y的key 则返回true。
    第二种情况有点费解: !(__y.first < __x.first) && __x.second < __y.second
    当然由于||运算具有短路作用,即当前面的条件不满足是,才进行第二种情况的判断 。第一种情况__x.first < __y.first 不成立,即__x.first >= __y.first 成立,在这个条件下,我们来分析下 !(__y.first < __x.first) && __x.second < __y.second
    !(__y.first < __x.first) ,看清出,这里是y的key不小于x的key ,结合前提条件,__x.first < __y.first 不成立,即x的key不小于y的key
    即: !(__y.first < __x.first) && !(__x.first < __y.first ) 等价于 __x.first == __y.first ,也就是说,第二种情况是在key相等的情况下,比较两者的value(second)。
    这里比较令人费解的地方就是,为什么不直接写__x.first == __y.first 呢? 这么写看似费解,但其实也不无道理:前面讲过,作为map的key必须实现<操作符的重载,但是并不保证==符也被重载了,如果key没有提供==,那么 ,__x.first == __y.first 这样写就错了。由此可见,stl中的代码是相当严谨的,值得我们好好研读。
    现在我们知道了pair类重载了<符,但是它并不是按照value进行比较的,而是先对key进行比较,key相等时候才对value进行比较。显然不能满足我们按value进行排序的要求。
    而且,既然pair已经重载了<符,而且我们不能修改其实现,又不能在外部重复实现重载<符。
typedef pair<string, int> PAIR;  
bool operator< (const PAIR& lhs, const PAIR& rhs) {  
    return lhs.second < rhs.second;  
}  

如果pair类本身没有重载<符,那么我们按照上面的代码重载<符,是可以实现对pair的按value比较的。现在这样做不行了,甚至会出错(编译器不同,严格的就报错)。
那么我们如何实现对pair按value进行比较呢? 第一种:是最原始的方法,写一个比较函数; 第二种:刚才用到了,写一个函数对象。这两种方式实现起来都比较简单。

typedef pair<string, int> PAIR;  
  
bool cmp_by_value(const PAIR& lhs, const PAIR& rhs) {  
  return lhs.second < rhs.second;  
}  
  
struct CmpByValue {  
  bool operator()(const PAIR& lhs, const PAIR& rhs) {  
   return lhs.second < rhs.second;  
  }  
};  

接下来,我们看下sort算法,是不是也像map一样,可以让我们自己指定元素间如何进行比较呢?

template <class RandomAccessIterator>  
  void sort ( RandomAccessIterator first, RandomAccessIterator last );  
template <class RandomAccessIterator, class Compare>  
  void sort ( RandomAccessIterator first, RandomAccessIterator last, Compare comp );

我们看到,令人兴奋的是,sort算法和map一样,也可以让我们指定元素间如何进行比较,即指定Compare。需要注意的是,map是在定义时指定的,所以传参的时候直接传入函数对象的类名,就像指定key和value时指定的类型名一样;sort算法是在调用时指定的,需要传入一个对象,当然这个也简单,类名()就会调用构造函数生成对象。
这里也可以传入一个函数指针,就是把上面说的第一种方法的函数名传过来。(应该是存在函数指针到函数对象的转换,或者两者调用形式上是一致的,具体确切原因还不明白,希望知道的朋友给讲下,先谢谢了。)
【参考代码】

int main() {  
  map<string, int> name_score_map;  
  name_score_map["LiMin"] = 90;  
  name_score_map["ZiLinMi"] = 79;  
  name_score_map["BoB"] = 92;  
  name_score_map.insert(make_pair("Bing",99));  
  name_score_map.insert(make_pair("Albert",86));  
 //把map中元素转存到vector中   
  vector<PAIR> name_score_vec(name_score_map.begin(), name_score_map.end());  
  sort(name_score_vec.begin(), name_score_vec.end(), CmpByValue());  
 // sort(name_score_vec.begin(), name_score_vec.end(), cmp_by_value);  
  for (int i = 0; i != name_score_vec.size(); ++i) {  
    cout << name_score_vec[i] << endl;  
  }  
  return 0;  
}  

【运行结果】
ZiLinMi 79
Albert 86
LiMin 90
BoB 92
Bing 99

typedef pair<string, int> PAIR;
  map<string, int> name_score_map;  
  vector<PAIR> name_score_vec(name_score_map.begin(), name_score_map.end());  
  sort(name_score_vec.begin(), name_score_vec.end(), CmpByValue());  

string 的分解

 // strToBread要分解的字串,strToken分解符,strVec分解完存储的vec
breakStringToVector(const string& strToBreak, const string& strToken, vector<QString>& strVec)
{
	bool bRet = true;
	char *namebuf = new char[strToBreak.length()+1];
	strcpy(namebuf, strToBreak.c_str());
	char* token = strtok( namebuf, strToken.c_str() ); // 分解
	while( token != NULL )
	{
		strVec.push_back(QString::fromLocal8Bit(token));
		token = strtok( NULL, strToken.c_str() ); // ??
	}
	delete[] namebuf;
	return bRet;
}

near 与 far

在80286以前的微处理器(CPU)组成的确16位机上,Windows操作系统(包括DOS)对于内存是分段使用的(分段内存模式,Segment Memory Mode)。运行在这些16位CPU微机上的Windows(Windows 1.0-3.1)被称为“Win16”。从80386开始的32位CPU开始,为了兼容,也采用上述分段内存模式,这就导致了near(short)、far(long)指针的出现。

从Windows 95开始的32位机上,Windows支持32位平面内存模式(与“分段内存模式”区别),相应地,Windows 95以后的Windows也就是我们常说的“Win32”。为Win32写的程序使用32位的线性地址空间。

由此可见,如果你想写在Win16上也能运行的程序,才会涉及到newr、far指针的概念。在Win32上,指针无near、far的区分。

存贮属性:C指针有三种存贮属性,分别是:
near (近)指针:16位段内偏移地址
far(远)指针:16位段地址+16位段内偏移地址
huge(巨)指针:32位规格化的具有唯一性的内存地址
C语言的存贮属性由六种编译模式决定(参见TC集成环境菜单中的option->compiler->model选项),默认的编译模式为small, 在该编译模式下,指针的默认属性为near。

函数也是这个道理
远指针是说指针所指向的地址已经超出了64K,所以需要使用DS加偏移量的方法来寻址,而不能直接寻址.其反义的修饰符是near.

整形溢出

示例一:整形溢出导致死循环

... ...
short len = 0;
... ...
while(len< MAX_LEN) {
    len += readFromInput(fd, buf);
    buf += len;
}

上面这段代码可能是很多程序员都喜欢写的代码(我在很多代码里看到过多次),其中的MAX_LEN 可能会是个比较大的整型,比如32767,我们知道short是16bits,取值范围是-32768 到 32767 之间。但是,上面的while循环代码有可能会造成整型溢出,而len又是个有符号的整型,所以可能会成负数,导致不断地死循环。

示例二:整形转型时的溢出

int copy_something(char *buf, int len)
{
	... ...
	... ...
    #define MAX_LEN 256
    char mybuf[MAX_LEN];
     ... ...
     ... ...
     if(len > MAX_LEN){ // <---- [1]
         return -1;
     }
 
     return memcpy(mybuf, buf, len);
}

上面这个例子中,还是[1]处的if语句,看上去没有会问题,但是len是个signed int,而memcpy则需一个size_t的len,也就是一个unsigned 类型。于是,len会被提升为unsigned,此时,如果我们给len传一个负数,会通过了if的检查,但在memcpy里会被提升为一个正数,于是我们的mybuf就是overflow了。这个会导致mybuf缓冲区后面的数据被重写。

示例三:分配内存

关于整数溢出导致堆溢出的很典型的例子是,OpenSSH Challenge-Response SKEY/BSD_AUTH 远程缓冲区溢出漏洞。下面这段有问题的代码摘自OpenSSH的代码中的auth2-chall.c中的input_userauth_info_response() 函数:

nresp = packet_get_int();
if (nresp > 0) {
    response = xmalloc(nresp*sizeof(char*));
    for (i = 0; i < nresp; i++)
        response[i] = packet_get_string(NULL);
}

上面这个代码中,nresp是size_t类型(size_t一般就是unsigned int/long int),这个示例是一个解数据包的示例,一般来说,数据包中都会有一个len,然后后面是data。如果我们精心准备一个len,比如:1073741825(在32位系统上,指针占4个字节,unsigned int的最大值是0xffffffff,我们只要提供0xffffffff/4 的值——0×40000000,这里我们设置了0×4000000 + 1), nresp就会读到这个值,然后nrespsizeof(char)就成了 1073741825 * 4,于是溢出,结果成为了 0×100000004,然后求模,得到4。于是,malloc(4),于是后面的for循环1073741825 次,就可以干环事了(经过0×40000001的循环,用户的数据早已覆盖了xmalloc原先分配的4字节的空间以及后面的数据,包括程序代码,函数指针,于是就可以改写程序逻辑。关于更多的东西,你可以看一下这篇文章《Survey of Protections from Buffer-Overflow Attacks》)。

示例四:缓冲区溢出导致安全问题

int func(char *buf1, unsigned int len1,
         char *buf2, unsigned int len2 )
{
   char mybuf[256]; 
 
   if((len1 + len2) > 256){    //<--- [1]
       return -1;
   } 
 
   memcpy(mybuf, buf1, len1);
   memcpy(mybuf + len1, buf2, len2); 
 
   do_some_stuff(mybuf); 
 
   return 0;
}

上面这个例子本来是想把buf1和buf2的内容copy到mybuf里,其中怕len1 + len2超过256 还做了判断,但是,如果len1+len2溢出了,根据unsigned的特性,其会与2^32求模,所以,基本上来说,上面代码中的[1]处有可能为假的。(注:通常来说,在这种情况下,如果你开启-O代码优化选项,那个if语句块就全部被和谐掉了——被编译器给删除了)比如,你可以测试一下 len1=0×104, len2 = 0xfffffffc 的情况。

这样的例子有很多很多,这些整型溢出的问题如果在关键的地方,尤其是在搭配有用户输入的地方,如果被黑客利用了,就会导致很严重的安全问题。

注意

前2种比较常见,后两种比较少见,愿意你是对于short,等16位的溢出比32位的溢出相对容易一些,也说明对于char(8位)的溢出会更加需要注意

规范

野指针问题

在delete内存后,需将指针指向NULL,原因在于,有的程序中,会根据指针是否为NULL 进行判断,然后进行处理,若不注意,这情况下,很可能造成程序崩溃。

内存管理 :新点:

5个分区

在C中,内存分成5个区,分别是: 堆、栈、自由存储区、全局/静态区和常量存储区.
栈:存放函数参数以及局部变量,在出作用域时,将自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限.\
堆:new分配的内存块(包括数组,类实例等),需delete手动释放.如果未释放,在整个程序结束后,OS会帮你回收掉.\
自由存储区:malloc分配的内存块,需free手动释放.它和堆有些相似.\
全局/静态区:全局变量(global)和静态变量(static)存于此处.(在以前的C语言中,全局变量又分为初始化的和未初始化的,C
不分)\
常量存储区:常量(const)存于此处,此存储区不可修改.\

堆与栈的区别

实例

void f()
{
       int *p = new int[5];
}

上面一段代码就包含了堆与栈.指针P被分配在了栈中,而new出来的东西则被分配在了堆中,此句可以解释为”在栈中存放了一个指向堆内存的指针p”.(

主要区别

管理方式不同: 栈是编译器自动管理的,堆需手动释放
空间大小不同: 在32位OS下,堆内存可达到4GB的的空间,而栈就小得可怜.(VC6中,栈默认大小是1M,当然,你可以修改它)
能否产生碎片不同:对于栈来说,进栈/出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的new/delete,会造成内存空间的不连续,容易产生碎片.
生长方向不同:栈向下生长,以降序分配内存地址;堆向上生长,以升序分配内在地址.
分配效率不同:栈是系统提供的数据结构,计算机会在底层对栈提供支持,进栈/出栈都有专门的指令,这就决定了栈的效率比较高.堆则不然,它由C/C++函数库提供,机制复杂,堆的效率要比栈低得多.

# 语言 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×