静水流长

常用链接

统计

最新评论

2010年9月26日 #

new/delete和malloc/free的联系和区别

相同点:都可用于申请动态内存和释放内存

不同点
(1)操作对象有所不同
malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符。对于非内部数据类的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数, 对象消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。

(2)在用法上也有所不同
函数malloc 的原型如下:
void * malloc(size_t size);
用malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
􀂋 malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
􀂋 malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。

函数free 的原型如下:
void free( void * memblock );
为什么free 函数不象malloc 函数那样复杂呢?这是因为指针p 的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p 是NULL 指针,那么free

对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。

new/delete 的使用要点
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。

如果用new 创建对象数组,那么只能使用对象的无参数构造函数。例如
Obj *objects = new Obj[100]; // 创建100 个动态对象
不能写成
Obj *objects = new Obj[100](1);// 创建100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
后者相当于delete objects[0],漏掉了另外99 个对象。


1  new自动计算需要分配的空间,而malloc需要手工计算字节数
2  new是类型安全的,而malloc不是,比如:
int* p = new float[2]; // 编译时指出错误
int* p = malloc(2*sizeof(float)); // 编译时无法指出错误
new operator 由两步构成,分别是 operator new 和 construct
3  operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上。而malloc无能为力
4  new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
5  malloc/free要库文件支持,new/delete则不要。 


1. 本质区别
malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。
对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete

 1class  Obj
 2{
 3public  :
 4      Obj( ) { cout  <<  “Initialization”  <<  endl; }
 5       ~ Obj( ) { cout  <<  “Destroy”  <<  endl; }
 6       void  Initialize( ) { cout  <<  “Initialization”  <<  endl; }
 7      void  Destroy( ) { cout  <<  “Destroy”  <<  endl; }
 8}
;
 9
10void  UseMallocFree( )
11{
12      Obj   * a  =  (obj  * ) malloc( sizeof ( obj ) );     //  allocate memory 
13      a -> Initialize();                                                 //  initialization
14       // … 
15      a -> Destroy();                                                 // deconstruction 
16      free(a);                                                           // release memory
17}

18
19void  UseNewDelete( void )
20{
21    Obj   * a  =   new  Obj;                                           
22     // … 
23    delete a; 
24}
类Obj的函数Initialize实现了构造函数的功能,函数Destroy实现了析构函数的功能。函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成“构造”与“析构”。所以我们不要用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

2. 联系
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete,malloc/free必须配对使用。

posted @ 2010-09-26 21:16 LinusYu 阅读(352) | 评论 (0)编辑 收藏

”Extern C“深入探索

1.引言

  C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。
  2.从标准头文件说起

  某企业曾经给出如下的一道面试题:

  面试题
  为什么标准头文件都有类似以下的结构?

 


#ifndef __INCvxWorksh
#define __INCvxWorksh 
#ifdef __cplusplus
extern "C" {
#endif 
/*...*/ 
#ifdef __cplusplus
}
#endif 
#endif /* __INCvxWorksh */


  分析
  显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。

  那么

#ifdef __cplusplus
extern "C" {
#endif 
#ifdef __cplusplus
}
#endif


  的作用又是什么呢?我们将在下文一一道来。
 
  3.深层揭密extern "C"

  extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。

  被extern "C"限定的函数或变量是extern类型的;

  extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:

  extern int a;


  仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

  通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

  与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

  被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;

  未加extern “C”声明时的编译方式

  首先看看C++中对类似C的函数是怎样编译的。

  作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );


  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

  _foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
  同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

  未加extern "C"声明时的连接方式

  假设在C++中,模块A的头文件如下:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif


  在模块B中引用该函数:

// 模块B实现文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);


  实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

  加extern "C"声明后的编译和连接方式

  加extern "C"声明后,模块A的头文件变为:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif


  在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

  (1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

  (2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

  如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

  所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
  实现C++与C及其它语言的混合编程。
  明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
  4.extern "C"的惯用法

  (1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"
{
#include "cExample.h"
}


  而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

  笔者编写的C++引用C函数例子工程中包含的三个文件的源代码如下:

/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C" 
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3); 
return 0;
}


编译方法:

首先把c文件编译成目标文件(.o),

gcc -c cExample.c

在编译C++文件:

g++ -o cppFile cppFile.cpp cExample.o


  如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。

  (2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
  笔者编写的C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 ); 
return 0;
}


  如果深入理解了第3节中所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。 

posted @ 2010-09-26 20:47 LinusYu 阅读(330) | 评论 (0)编辑 收藏

2010年9月20日 #

C++堆、栈、自由存储区、全局/静态存储区和常量存储区收藏(转载)

一个由c/C++编译的程序占用的内存分为以下几个部分 

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
2、堆区(heap) — 一般由程序员分配释放(malloc/free, new/delete),若程序员不释放,程序结束后可能由操作系统回收。注意它与数据结构中的堆石两回事,分配方式倒是类似与链表。其中有malloc/free分配释放的也可以叫自由存储区。
3、全局区(静态存储区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 (在C++中他们初始化和未初始化的共同占用一块内存区)。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 
5、程序代码区—存放函数体的二进制代码。


二、例子程序 
这是一个前辈写的,非常详细 
//main.cpp 
int a = 0; 全局初始化区 
char *p1; 全局未初始化区 
main() 

int b; 栈 
char s[] = "abc"; 栈 
char *p2; 栈 
char *p3 = "123456"; 123456在常量区,p3在栈上。 
static int c =0; 全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"); 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
}


二、堆和栈的理论知识 

堆和栈的区别主要有一下几点:
2.1管理方式不同 
stack: 
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 
heap: 
需要程序员自己申请,并指明大小,在c中malloc函数 
如p1 = (char *)malloc(10); 
在C++中用new运算符 
如p2 = (char *)malloc(10); 
但是注意p1、p2本身是在栈中的。


2.2 
申请后系统的响应 
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

2.3碎片问题

对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而产生大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是后进先出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在它弹出之前,在它上面的后进栈的内容已经被弹出。

2.4生长方向

对于堆来讲,生长方向是向上的,也就是想着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

2.5分配方式

堆是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配时编译器完成的,比如局部变量的分配。动态分配由函数alloca函数进行分配,但是栈的动态分配和堆石不同的,它的动态分配是由编译器进行释放,无需我们手工是想。

2.6 分配效率

栈是机器系统提供的数据结构,计算机会在底层堆栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是c/c++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆得效率比栈要低得多。

2.3申请大小的限制 
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。


2.4申请效率的比较: 
栈由系统自动分配,速度较快。但程序员是无法控制的。 
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. 
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

2.5堆和栈中的存储内容 
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

2.6存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在运行时刻赋值的; 
而bbbbbbbbbbb是在编译时就确定的; 
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 
比如: 
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

对应的汇编代码 
10: a = c[1]; 
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
0040106A 88 4D FC mov byte ptr [ebp-4],cl 
11: a = p[1]; 
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
00401070 8A 42 01 mov al,byte ptr [edx+1] 
00401073 88 45 FC mov byte ptr [ebp-4],al 
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。


2.7小结: 
堆和栈的区别可以用如下的比喻来看出: 
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。 
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

1、内存分配方面:

    堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式是类似于链表。可能用到的关键字如下:new、malloc、delete、free等等。

    栈:由编译器(Compiler)自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、申请方式方面:

    堆:需要程序员自己申请,并指明大小。在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符,但是注意p1、p2本身是在栈中的。因为他们还是可以认为是局部变量。

    栈:由系统自动分配。 例如,声明在函数中一个局部变量 int b;系统自动在栈中为b开辟空间。

3、系统响应方面:

    堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

4、大小限制方面:

    堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

    栈:在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

5、效率方面:

    堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

    栈:由系统自动分配,速度较快。但程序员是无法控制的。

6、存放内容方面:

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

    栈:在函数调用时第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈,然后是函数中的局部变量。 注意: 静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

7、存取效率方面:

    堆:char *s1 = "Hellow Word";是在编译时就确定的;

    栈:char s1[] = "Hellow Word"; 是在运行时赋值的;用数组比用指针速度要快一些,因为指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上直接读取。

posted @ 2010-09-20 09:02 LinusYu 阅读(358) | 评论 (0)编辑 收藏

2010年9月10日 #

C语言中的一些问题

C语言中的符号重载
符号 意义
static 在函数内部,表示该变量的值在各个调用间一直保持延续性
在函数这一级,表示该函数只对本文件可见
extern 用于函数定义,表示全局可见(属于冗余的)
用于变量,表示它在其他地方定义
void 作为函数的返回类型,表示不返回任何值
在指针声明中,表示通用指针的类型
位于参数列表中,表示没有参数
* 乘法运算符
用于指针,间接引用
在声明中,表示指针
& 位的AND操作符
取地址操作符
= 赋值操作符
==
比较运算符
<=
<<=
小于等于运算符
左移复合赋值运算符
< 小于运算符
#include指令的左界定符
() 在函数定义中,包围形式参数表。
调用一个函数
改变表达式的运算次序
将值转换为其他类型(强制类型转换)
定义带参数的宏
包围sizeof操作符的操作数(如果它是类型名)

C语言运算符优先级存在的问题

优先级问题 表达式 人们可能误以为的结果 实际结果
.的优先级高于*
->操作符用于消除
这个问题
*p.f p所指对象的字段f
(*p).f
对p取f偏移,作为指针,
然后进行解除引用的操作
*(p.f)
[]高于* int *ap[] ap是个指向int数组的指针
int(*ap)[]
ap是个元素为int指针的数组
int *(ap[])
函数()高于* int *fp() fp是个函数指针,所指函数
返回int。int(*fp)()
fp是个函数,返回int*
int *(fp())
==和!=高于位操作符 (val & mask != 0) (val & mask) != 0 val & (mask != 0)
==和!=高于赋值符 c = getchar()!=EOF (c = getchar()) != EOF c = (getchar() != EOF)
算术运算高于移位运算符 msb << 4 + lsb (msb << 4) + lsb msb << (4 + lsb)
逗号运算符在所有运算符中
优先级最低
i = 1, 2 i = (1, 2) (i = 1), 2

posted @ 2010-09-10 21:59 LinusYu 阅读(202) | 评论 (0)编辑 收藏

关于无符号类型的建议

(摘自C专家编程)
    字符和整型(整型升级)
    char,short int或者int型位段(bit-field),包括它们的有符号或无符号类型,以及枚举类型,可以使用在需要int或unsigned int的表达式中。如果int可以完整表示源类型的所有值(即int是32位,译者注),那么该源类型的值就转换为int,否则转换为unsigned int。这称为整型升级。
    寻常算术转换
    许多操作数类型为算术类型的双目运算符会引发转换,并以类似的方式产生结果类型。它的目的是产生一个普通类型,同时也是运算结果的类型,这个模式称为“寻常算术转换“。
    首先,如果其中一个操作数是long double,那么另一个操作数也被转换为long double。其次,如果其中一个操作数的类型是double,那么另一个操作数也被转换为double。再次,如果其中一个操作数的类型是float,那么另一个操作数也被转换为float。否则,两个操作数执行整型升级,执行下面的规则:
    如果其中一个操作数的类型是unsigned long int,那么另一个操作数也被转换为unsigned long int。其次,如果其中一个操作数的类型是long int,而另一个操作数的类型是unsigned int,如果long int能够完整表示unsigned int的所有值(即long是32位而int是16位,译者注),那么unsigned int类型操作数被转换为long int,如果long int不能完整表示unsigned int的所有值(即long和int均为32位,译者注),那么两个操作数都被转换为unsigned long int。再次,如果其中一个操作数是long int,那么另一个操作数被转换为long int。再再次,如果其中一个操作数的类型是unsigned int,那么另一个操作数被转换为unsigned int。如果所有以上情况都不属于,那么两个操作数都为int。
    通俗的说(当然存在漏洞,而且不够精确):当执行算术运算时,操作数的类型如果不同,就会发生转换。数据类型一般朝着浮点精度更高、长度更长的方向转换,整型数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。
    ANSI C标准采用值保留(value preserving)原则,就是当把几个整型操作数混合使用,结果类型有可能是有符号数,也有可能是无符号数,取决于操作数的类型的相对大小。
对无符号类型的建议:
    尽量不要在代码中使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在赋值(如年龄、国债)而用它来表示数量。
    尽量使用像int那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况(如-1被翻译成非常大的正数)。
    只有在使用位段和二进制掩码时,才可以使用无符号数。应该在表达式中使用强制类型转换,使操作数均为有符号数或者无符号数,这样就不必由编译器来选择结果的类型。

posted @ 2010-09-10 20:24 LinusYu 阅读(367) | 评论 (0)编辑 收藏

【转载】c++网络资源

1. 组织机构
  国际标准化组织 http://www.iso.org/
  中国知识产权网 http://www.cnipr.com/
  全国计算机等级考试网 http://www.ncre.cn/ncre_new/index.html
  国家计算机病毒应急处理中心 http://www.antivirus-china.org.cn/
  2. C/C++资源
  C++标准程序库 http://www.ccd.bnl.gov/bcf/cluster/pgi/pgC++_lib/stdlib.htm
  C++标准 http://std.dkuug.dk/jtc1/sc22/wg21/docs/standards
  C99标准 http://www.nirvani.net/docs/ansi_c.pdf
  Cprogramming.com http://www.cprogramming.com/
  Dr. Dobb's Journal http://www.ddj.com/
  C/C++ Users Journal http://www.cuj.com/
  C维视点 http://www.c-view.org
  C语言之家 http://www.cstudyhome.com/wenzhang06/
  维C世界 http://www.vcok.com/
  C/C++编码规范 http://www.fjmcu.com/html/mixed/c++.html
  Boost http://www.boost.org/
  ACE http://www.cs.wustl.edu/~schmidt/ACE.html
  Dev-C++ http://www.c-view.org/soft/devcpp/index.html
  STLport http://www.stlport.org/
  SGI STL http://www.sgi.com/tech/stl/
  Dinkum C++ Library http://www.dinkumware.com/
  Qt http://www.trolltech.com/
  Qt中文网 http://www.qiliang.net/qt.html
  3. 个人站点(来源于jjhou.com)
  Bjarne Stroustrup http://www.research.att.com/~bs/
  Stanley B. Lippman http://staff.develop.com/slip/
  David Musser http://www.cs.rpi.edu/~musser/
  Scott Meyers http://www.aristeia.com/
  Nicolai M. Josuttis http://www.josuttis.com/
  Bruce Eckel http://www.mindview.net
  Herb Sutter http://www.gotw.ca/
  Herb Sutter http://www.gotw.ca/
  Andrei Alexandrescu http://www.moderncppdesign.com
  候捷个人网站 http://www.jjhou.com/
  ////////////////////////////////////////////////////////////////////////////////////
  网络协议:
http://www.cnpaf.net/class/yingyong/
http://www.cnithr.com/
  英文站:
http://www.codeguru.com
http://www.rayoko.com/
http://www.flyingspace.com/code/code_index.html
http://www.pcvc.net/oldcode/
http://dev.csdn.net/
http://202.201.139.1/COMPUTER/SOFTWARE/Visual%20C%20C++/index.htm
http://www.vckbase.com/
  c++视频:
http://www.aqiao.net/viewthread.php?fpage=1&tid=1963
http://www.vczx.com/
http://www.cosoft.org.cn/html/osl/browse.php?form_cat=2&page=950
  ftp资源
http://www.cenxi.net/Announce/Announce.asp?BoardID=13&ID=255144 
http://www.infoxa.com/asp/tszy/tszy.asp?page=2&px=
http://www.ddvip.net/program/c++/index.asp
http://jpk.shcemt.edu.cn/cplus/kejian/CONTENT/INDEX.HTM
http://www.vvsoft.net/vvbksd/index.asp
http://bt2.5qzone.net/html/index_c11_p0.html
http://www.99baidu.com/soft/48497.htm
http://www.yesky.com/SoftChannel/72348977504190464/20030418/1664412.shtml
  几本书:
  《windows程序设计》
  《windows核心编程》
  《深入浅出MFC》
  《mfc windows程序设计》
  代码:
http://www.czvc.com/down.asp?id=81
http://www.arongsoft.com/2004/5-15/224219.htm
  MSDN中文站
http://www.microsoft.com/china/msdn/
http://www.yesky.com/SoftChannel/72348977504190464/20030418/1664412.shtm(王朝网络 wangchao.net.cn)

posted @ 2010-09-10 12:09 LinusYu 阅读(265) | 评论 (0)编辑 收藏

2010年9月2日 #

gdb调试常用命令

    使用 gcc -Wall -pedantic -ansi 这些选项将启用许多警告和其他检查来检验程序是否符合C语言标准。
    gdb所有版本都支持“空命令“,即直接按下回车键再次执行最近执行过的那条命令。再用step或next命令单步执行程序时,这个“空命令“非常有用。
    run:运行一个程序,在run命令中的给出的所有参数都将作为程序的参数传递给程序。
    backtrace:可以查看程序是如何到达这一位置的。
    print:给出变量和其他表达式的内容。可以用print命令的表达式来查看处理过的数组元素,gdb允许我们使用几乎所有合法的C语言表达式来打印变量、数组元素和指针的取值。gdb将命令的结果保存在伪变量$<number>中。最后一次操作的结果总是为$,倒数第二次操作的结果为$$。这使得我们可以把某次操作的结果用在另一个命令中。例如:
    (gdb) print j
    $3 = 4
    (gdb) print a[$-1].key
    $4 = 1
    要打印出一组连续的数据项,可以使用@<number>让gdb打印除指定书目的数组元素。例如:
    (gdb) print array[0]@5
    list:这个命令会打印出围绕当前位置前后的一段代码,如果继续使用list命令,会显示更多的代码。我们也可以给list命令提供一个行号活函数名作为参数,它将显示指定位置前后的代码。
    break:设置断点。此外。我们可以修改断点设置,使程序不是在断点处停下来,而只是显示要查看的数据,然后继续执行。我们用commands命令来完成这一工作。它的作用是指定程序到达断点位置时需要执行的调试器命令。
    (gdb) commands 2
    Type commands for when breakpoint 2 is hit, one per line. 
    End with a line saying just "end".
    > dispaly
    > cont
    > end
    通过将断点的设置与相应的操作结合起来,就可以尝试修改程序(也被称为打补丁)而不需要修改程序的源代码并重新编译。可以用info命令查看曾经设置过的断点及display命令的内容,如下所示:
    (gdb) info display
    Auto-display expressions now in effect:
    Num   Enb   Expression
    1:    y    array[0] @ 5
    (gdb) info break
    Num  Type        Disp  Enb   Address     What
    1    breakpoint  keep  y     0x08048427  in sort at debug1.c:21
         breakpoint already hit 3 times
         cont
     设置命令如下:
     (gdb) commands 2
     Type commands for when breakpoint 2 is hit, one per line.
     End with a line saying just "end".
     > set variable n = n + 1
     > cont
     > end

posted @ 2010-09-02 19:13 LinusYu| 编辑 收藏

2010年8月10日 #

C/C++语言void 及void 指针深层探索

1 void 的含义
void 的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。
void 几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void 变量,让我们试着来定义:
void a;
这行语句编译时会出错,提示“illegal use of type ‘void‘”。不过,即使void a 的编译不会出错,它也没有任何实际意义。
void 真正发挥的作用在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。

2 下面给出void 关键字的使用规则:

规则一 如果函数没有返回值,那么应声明为void 类型

在C 语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void 类型。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
程序运行的结果为输出:
2 + 3 = 5
这说明不加返回值说明的函数的确为int 函数。
林锐博士《高质量C/C++编程》中提到:“C++语言有很严格的类型安全检查,不允许上述情况(指函数不加类型声明)发生”。可是编译器并不一定这么认定,譬如在Visual C++6.0 中上述add 函数的编译无错也无警告且运行正确,所以不能寄希望于编译器会做严格的类型检查。
因此,为了避免混乱,我们在编写C/C++程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void 类型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上void 类型声明后,也可以发挥代码的“自注释”作用。代码的“自注释”即代码能自己注释自己。

规则二 如果函数无参数,那么应声明其参数为void
在C++语言中声明一个这样的函数:
int function(void)
{
return 1;
}
则进行下面的调用是不合法的:
function(2);
因为在C++中,函数参数为void 的意思是这个函数不接受任何参数。
我们在Turbo C 2.0 中编译:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
编译正确且输出1,这说明,在C 语言中,可以给无参数的函数传送任意类型的参数,但是在C++编译器中编译同样的代码则会出错。
在C++中,不能向无参数的函数传送任何参数,出错提示“‘fun‘ : function does not take 1 parameters”。
所以,无论在C 还是C++中,若函数不接受任何参数,一定要指明参数为void。

规则三 小心使用void 指针类型
按照ANSI(American National Standards Institute)标准,不能对void 指针进行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//ANSI 标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
//例如:
int *pint;
pint++; //ANSI:正确
pint++的结果是使其增大sizeof(int)。
但是大名鼎鼎的GNU(GNU‘s Not Unix 的缩写)则不这么认定,它指定void *的算法操作与char *一致。
因此下列语句在GNU 编译器中皆正确:
53
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
pvoid++的执行结果是其增大了1。
在实际的程序设计中,为迎合ANSI 标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确
GNU 和ANSI 还有一些区别,总体而言,GNU 较ANSI 更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合ANSI 标准。

规则四 如果函数的参数可以是任意类型指针,那么应声明其参数为void *
典型的如内存操作函数memcpy 和memset 的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
这样,任何类型的指针都可以传入memcpy 和memset 中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy 和memset 的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的memcpy 和memset 明显不是一个“纯粹的,脱离低级趣味的”函数!
下面的代码执行正确:
//示例:memset 接受任意类型指针
int intarray[100];
memset ( intarray, 0, 100*sizeof(int) ); //将intarray 清0
//示例:memcpy 接受任意类型指针
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100*sizeof(int) ); //将intarray2 拷贝给intarray1
有趣的是,memcpy 和memset 函数返回的也是void *类型,标准库函数的编写者是多么地富有学问啊!

规则五 void 不能代表一个真实的变量
下面代码都企图让void 代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void 体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(还有人妖?)。
void 的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void 数据类型。正如
不能给抽象基类定义一个实例,我们也不能定义一个void(让我们类比的称void 为“抽象数据类型”)变量。

总结
小小的void 蕴藏着很丰富的设计哲学,作为一名程序设计人员,对问题进行深一个层次的思考必然使我们受益匪浅。

posted @ 2010-08-10 16:07 LinusYu| 编辑 收藏

仅列出标题