2011年2月5日星期六

<转>volatile关键字

  可能很多人都没用过C/C++中的这个关键词,甚至不知道它的存在,本人以前也只是有所耳闻,但似懂非懂。
  这是一个类型修饰符,位置同const、static等。一个使用volatile修饰的变量,比如volatile int i; 每次对该变量的直接引用,都会访问内存,而不是从寄存器中读取(如果其已经在寄存器中)。这样一来,volatile似乎没什么用处,反倒会使数据的读取相对变慢很多。但是,如果没有volatile,编译器可能会优化你的程序,使得数据从寄存器中读取,从而加快程序的运行,但如果这个变量是同其它进程/线程共享的,就可能造成数据的不一致。多线程情况下,你可以使用互斥机制来保证对共享数据访问的原子性。但是,在单片机等嵌入式环境中,硬件经常不会有这种互斥机制的支持,这时某些共享的数据(比如端口)就可能会产生不一致的情况。而使用volatile就会使编译器不对代码进行优化,每次对该变量的访问都会从内存中读取。
  下面通过观察使用volatile前后编译产生的汇编代码的不同,来加深对volatile关键词的理解。
  对于下面的C代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
 
int foo(void)
{
    int i = 0;
    while(i++ < 20);
    return i;
}
int
main(int argc, char **argv)
{
    int i = foo();
    int j = i;
    int k = i;
    printf("%d, %d, %d\n", i, j, k);
    return 0;
}
  代码中,使用foo()的返回值(20)来初始化整型变量i。之所以不直接赋值,是为了防止编译器优化,下面对j,k的赋值将直接使用20,而不是i,也不是寄存器。使用下面的命令编译:
1
$ gcc -S -O1 main.c # 优化等级1
  产生的汇编代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ ... ]
.LC0:
 .string "%d, %d, %d\n"
[ ... ]
main:
[ ... ]
 call foo # 调用函数foo,返回值保存至寄存器eax
 movl %eax, 16(%esp) # 参数k入栈
 movl %eax, 12(%esp) # 参数j入栈
 movl %eax, 8(%esp) # 参数i入栈
 movl $.LC0, 4(%esp) # 格式字符串地址入栈
 movl $1, (%esp)
 call __printf_chk
 movl $0, %eax
 leave
 ret
[ ... ]
  可以看到,对j,k的赋值都是由寄存器eax进行的。下面把代码中的int i = foo();换成volatile int i = foo();,编译得到的汇编代码是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ ... ]
main:
[ ... ]
 call foo # 调用函数foo,返回值保存至寄存器eax
 movl %eax, 44(%esp) # 为i赋值
 movl 44(%esp), %edx # 读取i值
 movl 44(%esp), %ecx # 读取i值
 movl 44(%esp), %eax # 读取i值
 movl %ecx, 16(%esp) # 参数k入栈,使用i值
 movl %edx, 12(%esp) # 参数j入栈,使用i值
 movl %eax, 8(%esp) # 参数i入栈
 movl $.LC0, 4(%esp) # 格式字符串地址入栈
 call __printf_chk
 movl $0, %eax
 leave
 ret
[ ... ]
  可以看到,每次对i的访问都是从内存(栈)中读取的。

volatile vs. const

  正像上面所描述的那样,volatile的意思是“易变的”。C/C++中还有另外一个关键词叫const,用来限定变量不可被改变。似乎volatile和const意思相反,水火不容。但volatile const int N = 0;这样的语句是允许存在的。为什么呢?其实,这个语句可以这样来理解:变量N在本程序(或者设备)中是不可(禁止)改变的,但它可能会被其它程序(或者设备)改变。你或许知道,const只是编译器的限定,而没有执行期的检查,因此const也只是限于单个进程,因此volatile和const这两个关键词并不冲突。
  另外,普通的整型const变量,比如const int N = 10; int a[N]; 中,变量N可能并不会被分配内存(除非程序里面有显式使用N地址的表达式)。但是,根据volatile变量的特性,volatile const int N = 10;中变量N是一定会占用内存的。这一点,亦可以通过观察相应的汇编代码来验证。

2011年2月4日星期五

<转>*string++、*++string、++*string 的区别

*string++、*++string、++*string的区别在于运算符的运算顺序不同。
  根据运算符的结合 性,*string++等价于*(string++),即取出当前字符后将指针后移;
  *++string和++*string就很明显 了,前者表示指针后移,并返回所指向的字符,后者表示将string所指向的字符累加1。

<转>aligned malloc 实现

I checked it it works !!
#include <stdio.h>
#include <stdlib.h>

/*************************************************
Name :- aligned_malloc
Arguments:- number of bytes & Alignment Boundry
Return :- NULL on error
valid pointer on success
Working :- It will allocate memory with starting address
multiple of alignment passed and returns pointer
to it on success.
Ex.
aligned_malloc(50,128);
This will allocate 50 bytes of memory with
starting address multiple of 128.
*************************************************/
void *aligned_malloc(size_t bytes, size_t alignment)
{
        void *p1 ,*p2; // basic pointer needed for computation.

        if((p1 =(void *) malloc(bytes + alignment + sizeof(size_t)))==NULL)
                return NULL;

        size_t addr=(size_t)p1+alignment+sizeof(size_t);
        p2=(void *)(addr - (addr%alignment));

        *((size_t *)p2-1)=(size_t)p1;
        return p2;
}

/************************************************
Name :- aligned_free
Arguments :- pointer to be freed
Returns :- Nothing
*************************************************/
void aligned_free(void *p )
{
        free((void *)(*((size_t *) p-1)));
}

memalign函数可以直接实现这个功能,限制是alignment必须是2的power

2011年2月2日星期三

<转>C++静态成员函数小结

一 静态数据成员:

类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静 态数据成员也遵守public/protected/private访问规则。同时,静态数据成员还具有以下特点:

1.静态数据成员的 定义。
静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。
其定义方式与全局变量相 同。举例如下:

xxx.h文件
class base{
private:
static const int _i;//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。
};

xxx.cpp文件
const int base::_i=10;//定义(初始化)时不受private和protected访问限制.

注:不要试图在头文件中定 义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。

2.静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。举例如下:
class base{
public :
static int _num;//声明
};
int base::_num=0;//静态数据成员的真正定义

class derived:public base{
};

main()
{
base a;
derived b;
a._num++;
cout<<"base class static data number _num is"<<a._num<<endl;
b._num++;
cout<<"derived class static data number _num is"<<b._num<<endl;
}
// 结果为1,2;可见派生类与基类共用一个静态数据成员。

3.静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。举例 如下:
class base{
public :
static int _staticVar;
int _var;
void foo1(int i=_staticVar);//正确,_staticVar为静态数据成员
void foo2(int i=_var);//错误,_var为普通数据成员
};

4.★静态数据成员的类型可以是所属类的类型,而 普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用。举例如下:

class base{
public :
static base _object1;//正确,静态数据成员
base _object2;//错误
base *pObject;//正确,指针
base &mObject;//正确,引用
};

5.★这个特性,我不 知道是属于标准c++中的特性,还是vc6自己的特性。
静态数据成员的值在const成员函数中可以被合法的改变。举例如下:

class base{
public:
base(){_i=0;_val=0;}

mutable int _i;
static int _staticVal;
int _val;
void test() const{//const 成员函数

_i++;// 正确,mutable数据成员
_staticVal++;//正确,static数据成员
_val++;//错误

}
};
int base::_staticVal=0;

二,静态成员函数
静态成员函数没有什么太多好讲 的。

1.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。举例如下:
class base{
static int func1();
int func2();
};

int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针


2.静态成员函数不可以调用类的非静态成 员。因为静态成员函数不含this指针。

3.静态成员函数不可以同时声明为 virtual、const、volatile函数。举例如下:
class base{
virtual static void func1();//错误
static void func2() const;//错误
static void func3() volatile;//错误
};


最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可 以访问。

<转>C++中的mutable关键字

    mutalbe的中文意思是“可变的,易 变的”,跟constant(既C++中的const)是反义词。
    在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。
    我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const的函数里面修改一些跟 类状态无关的数据成员,那么这个数据成员就应该被mutalbe来 修饰。
    下面是一个小例子:
class ClxTest
{
public:
    
void Output() const;
};
void ClxTest::Output() const
{
    cout 
<< "Output for test!" << endl;
}
void OutputTest(const ClxTest& lx)
{
    lx.Output();
}
    类ClxTest的成员函数Output是用来输出的,不会修改类的状态,所以被声明为const的。
    函数OutputTest也是用来输出的,里面调用了对象lx的Output输出方法,为了防止在函数中调用其他成员函数修改任何成员变量,所以参数也被 const修饰。
    如果现在,我们要增添一个功能:计算每个对象的输出次数。如果用来计数的变量是普通的变量的话,那么在const成员函数Output里面是不能修改该变 量的值的;而该变量跟对象的状态无关,所以应该为了修改该变量而去掉Output的const属性。这个时候,就该我们的mutable出场了——只要用mutalbe来修饰这个变量,所有问题就迎刃而解了。
    下面是修改过的代码:
class ClxTest
{
public:
    ClxTest();
    
~ClxTest();

    
void Output() const;
    
int GetOutputTimes() const;
private:
    mutable 
int m_iTimes;
};

ClxTest::ClxTest()
{
    m_iTimes 
= 0;
}

ClxTest::
~ClxTest()
{
}
void ClxTest::Output() const
{
    cout 
<< "Output for test!" << endl;
    m_iTimes
++;
}
int ClxTest::GetOutputTimes() const
{
    
return m_iTimes;
}
void OutputTest(const ClxTest& lx)
{
    cout 
<< lx.GetOutputTimes() << endl;

    lx.Output();

    cout 
<< lx.GetOutputTimes() << endl;
}
    计数器m_iTimes被mutable修 饰,那么它就可以突破const的限制,在被const修饰的函数里面也能被修改。

2011年2月1日星期二

<转>实例详解C++友元

  在说明什么是友元之前,我们先说明一下为什么需要友元与友元的缺点:

  通常对于普通函数来说,要访问类的保护成员是不可能的,如果想这么做那么必须把类的成员都生命成为public(共用的),然而这做带来的问题遍是任何外部函数都可以毫无约束的访问它操作它,c++利用friend修饰符,可以让一些你设定的函数能够对这些保护数据进行操作,避免把类成员全部设置成public,最大限度的保护数据成员的安全。   友元能够使得普通函数直接访问类的保护数据,避免了类成员函数的频繁调用,可以节约处理器开销,提高程序的效率,但矛盾的是,即使是最大限度的保护,同样也破坏了类的封装特性,这即是友元的缺点,在现在cpu速度越来越快的今天我们并不推荐使用它,但它作为c++一个必要的知识点,一个完整的组成部分,我们还是需要讨论一下的。

  在类里声明一个普通函数,在前面加上friend修饰,那么这个函数就成了该类的友元,可以访问该类的一切成员。

  下面我们来看一段代码,看看我们是如何利用友元来访问类的一切成员的。
#include <iostream>   
using namespace std; 
class Internet   
{   
public:   
    Internet(char *name,char *address)   
    {   
        strcpy(Internet::name,name);   
        strcpy(Internet::address,address);    
    } 
friend void ShowN(Internet &obj);//友元函数的声明 
public:   
    char name[20]; 
    char address[20]; 
}; 
 
 
void ShowN(Internet &obj)//函数定义,不能写成,void Internet::ShowN(Internet &obj) 
{ 
    cout<<obj.name<<endl; 
} 
void main()   
{ 
    Internet a("中国软件开发实验室","www.cndev-lab.com"); 
    ShowN(a); 
    cin.get(); 
}
  上面的代码通过友元函数的定义,我们成功的访问到了a对象的保护成员name,友元函数并不能看做是类的成员函数,它只是个被声明为类友元的普通函数,所以在类外部函数的定义部分不能够写成void Internet::ShowN(Internet &obj),这一点要注意。

  一个普通函数可以是多个类的友元函数,对上面的代码我们进行修改,注意观察变化:
#include <iostream>   
using namespace std; 
class Country; 
class Internet   
{   
public:   
    Internet(char *name,char *address)   
    {   
        strcpy(Internet::name,name);   
        strcpy(Internet::address,address);    
    } 
friend void ShowN(Internet &obj,Country &cn);//注意这里 
public:   
    char name[20]; 
    char address[20]; 
}; 
 
class Country 
{ 
public: 
    Country() 
    { 
        strcpy(cname,"中国"); 
    } 
friend void ShowN(Internet &obj,Country &cn);//注意这里 
protected: 
    char cname[30]; 
}; 
 
void ShowN(Internet &obj,Country &cn) 
{ 
    cout<<cn.cname<<"|"<<obj.name<<endl; 
} 
void main()   
{ 
    Internet a("中国软件开发实验室","www.cndev-lab.com"); 
    Country b; 
    ShowN(a,b); 
    cin.get(); 
}
  一个类的成员函数函数也可以是另一个类的友元,从而可以使得一个类的成员函数可以操作另一个类的数据成员,我们在下面的代码中增加一类Country,注意观察:
#include <iostream>   
using namespace std; 
class Internet; 
 
class Country 
{ 
public: 
    Country() 
    { 
        strcpy(cname,"中国"); 
    } 
    void Editurl(Internet &temp);//成员函数的声明 
protected: 
    char cname[30]; 
}; 
class Internet 
{   
public:   
    Internet(char *name,char *address)   
    {   
        strcpy(Internet::name,name);   
        strcpy(Internet::address,address);  
    } 
    friend void Country::Editurl(Internet &temp);//友元函数的声明 
protected:   
    char name[20]; 
    char address[20]; 
}; 
void Country::Editurl(Internet &temp)//成员函数的外部定义 
{ 
    strcpy(temp.address,"edu.cndev-lab.com"); 
    cout<<temp.name<<"|"<<temp.address<<endl; 
} 
void main() 
{ 
    Internet a("中国软件开发实验室","www.cndev-lab.com"); 
    Country b; 
    b.Editurl(a); 
    cin.get(); 
}
  整个类也可以是另一个类的友元,该友元也可以称做为友类。友类的每个成员函数都可以访问另一个类的所有成员。
  示例代码如下:

#include <iostream>   
using namespace std; 
class Internet; 
 
class Country 
{ 
public: 
    Country() 
    { 
        strcpy(cname,"中国"); 
    } 
    friend class Internet;//友类的声明 
protected: 
    char cname[30]; 
}; 
class Internet 
{ 
public:   
    Internet(char *name,char *address)   
    {   
        strcpy(Internet::name,name);   
        strcpy(Internet::address,address);  
    } 
    void Editcname(Country &temp); 
protected:   
    char name[20]; 
    char address[20]; 
}; 
void Internet::Editcname(Country &temp) 
{ 
    strcpy(temp.cname,"中华人民共和国");  
} 
void main() 
{ 
    Internet a("中国软件开发实验室","www.cndev-lab.com"); 
    Country b; 
    a.Editcname(b); 
    cin.get(); 
}
  在上面的代码中我们成功的通过Internet类Editcname成员函数操作了Country类的保护成员cname。

  在编程中,我们使用友元的另外一个重要原因是为了方便重载操作符的使用,这些内容我们将在后面的教程着重讨论!

<转>C++中 public,protected, private 访问标号小结

C++中 public,protected, private 访问标号小结

第一:private, public, protected 访问标号的访问范围。
private:只能由1.该类中的函数、2.其友元函数访问。
不能被任何其他访问,该类的对象也不能访问。

protected:可以被1.该类中的函数、2.子类的函数、以及3.其友元函数访问。
但不能被该类的对象访问。

public:可以被1.该类中的函数、2.子类的函数、3.其友元函数访问,也可以由4.该类的对象访问。
注:友元函数包括3种:设为友元的普通的非成员函数;设为友元的其他类的成员函数;设为友元类中的所有成员函数。

第二:类的继承后方法属性变化。
private 属性不能够被继承。使用private继承,父类的protected和public属性在子类中变为private;
使用protected继承,父类的protected和public属性在子类中变为protected;
使用public继承,父类中的protected和public属性不发生改变;