一段简单的C++代码在VC6.0没问题,但是在VC2017出错,求分析原因!

uouo88 2019-03-04 04:57:53


如图,在VC6.0中没有问题,在VC2017编译出错,试了一下,只要把public CExpetcion 改成 protected CExpetcion,或者将throw BatchException("~ServiceHandle"); 改为 throw new BatchException("~ServiceHandle"); 就没有这个link报错的问题,为什么throw普通对象的时候会去触发CObject的复制构造方法?求分析原因!
...全文
276 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
pstrunner 2019-03-05
  • 打赏
  • 举报
回复
引用 10 楼 uouo88 的回复:
[quote=引用 8 楼 pstrunner 的回复:]

void main()
{
	CMyException tst("hello"), tst2;
	// 没有问题
	tst2 = tst;
	// 报错
	throw tst;
}
是的,我用你的代码试了,这里只要加上throw tst;就出错,可是为什么呢,你前main函数里面两句代码分别测试了构造方法和赋值构造方法,都没有问题,甚至我在你代码的基础上,“throw tst; ”前加上一句CMyException tst3(tst2);再编译也是没有问题的,说明单独调用拷贝构造函数也不会错。[/quote] 我在上面已经回答了,估计是VC问题,你参考下面的说明: The throw of an instance of a class object causes a copy to be generated when the object is copied into the C++ runtime memory location that holds the exception object. It may be possible for some compilers to "elide" (get rid of) this call to the copy-constructor in a similar way to which they can get rid of the call to a copy-constructor when returning an instance of a class object from a function (the RVO optimization) - but the compiler still needs to check that the copy-constructor is callable: so if the copy-constructor is private it should still cause an error even though the compiler would not actually generate a call to it.
uouo88 2019-03-05
  • 打赏
  • 举报
回复
引用 8 楼 pstrunner 的回复:

void main()
{
CMyException tst("hello"), tst2;
// 没有问题
tst2 = tst;
// 报错
throw tst;
}


是的,我用你的代码试了,这里只要加上throw tst;就出错,可是为什么呢,你前main函数里面两句代码分别测试了构造方法和赋值构造方法,都没有问题,甚至我在你代码的基础上,“throw tst; ”前加上一句CMyException tst3(tst2);再编译也是没有问题的,说明单独调用拷贝构造函数也不会错。
uouo88 2019-03-05
  • 打赏
  • 举报
回复
补充测试代码如下

#include "pch.h"
#include "afx.h"
#include <iostream>
using namespace std;

class BaseException
{
public:
BaseException() throw() {cout << "Constructor BaseException() is called" << endl;}
~BaseException() throw() { cout << "Destructor ~BaseException() is called" << endl; }
//virtual ~BaseException() = 0;
private:
BaseException(const BaseException& be) throw() // = delete;
{
cout << "Copy-Constructor BaseException(const BaseException& be) is called" << endl;
}
};

class BatchException : public BaseException //public CException
{
private:
CString m_cdMsg = "";
public:
BatchException() throw() { cout << "Constructor BatchException() is called" << endl; }
BatchException(const char* p_szMes) throw()
{
m_cdMsg = p_szMes;
cout << "Constructor BatchException(const char* p_szMes) is called" << endl;
}
BatchException(const BatchException& be) throw()
{
this->m_cdMsg = be.m_cdMsg;
cout << "Copy-Constructor BatchException(const BatchException& be) is called" << endl;
}
const BatchException& operator = (const BatchException& be) throw()
{
this->m_cdMsg = be.m_cdMsg; return *this;
cout << "Assign-Constructor BatchException(const BatchException& be) is called" << endl;
}
virtual ~BatchException() throw() { cout << "Destructor ~BatchException() is called" << endl; }
virtual const char *what() const throw() { return m_cdMsg; }
};

int main()
{
try
{
//BatchException be("BatchException occurs");
//throw be;
throw BatchException("BatchException occurs");
//throw new BatchException("BatchException occurs");
}
catch (BatchException& e)
{
cout << "e.what() = " <<e.what() << endl;
}
//catch (CException* e)
//{
// cout << "e->what() = " << ((BatchException*)e)->what() << endl;
// e->Delete();
//}
//pause:
getchar();
}
pstrunner 2019-03-05
  • 打赏
  • 举报
回复

void main()
{
	CMyException tst("hello"), tst2;
	// 没有问题
	tst2 = tst;
	// 报错
	throw tst;
}
pstrunner 2019-03-05
  • 打赏
  • 举报
回复
我也写了测试代码:

class CMyException : public CException
{
public:
	CMyException() {}
	CMyException(const CMyException& x) {
		*this = x;
	}
	explicit CMyException(const char* szMsg) {}
	~CMyException() {}
	CMyException& operator=(const CMyException& x) {
		return *this;
	}
private:

};
uouo88 2019-03-05
  • 打赏
  • 举报
回复
引用 5 楼 pstrunner 的回复:
[quote=引用 4 楼 uouo88 的回复:]
[quote=引用 3 楼 pstrunner 的回复:]
我查了下,估计这是VC的一个问题,msdn论坛解释如下:【https://social.msdn.microsoft.com/Forums/vstudio/en-US/a7dfa49c-6b81-4ce1-86a3-a926c700d626/yet-another-c2248-error?forum=vcgeneral】

The throw of an instance of a class object causes a copy to be generated when the object is copied into the C++ runtime memory location that holds the exception object.

It may be possible for some compilers to "elide" (get rid of) this call to the copy-constructor in a similar way to which they can get rid of the call to a copy-constructor when returning an instance of a class object from a function (the RVO optimization) - but the compiler still needs to check that the copy-constructor is callable: so if the copy-constructor is private it should still cause an error even though the compiler would not actually generate a call to it.

If you throw a pointer to an object, as in your code, then you need to catch a pointer. For example:

class X { };

void f()
{
throw new X();
}

void g()
{
try {
f();
}
catch (X* pX) {
delete pX;
}
}

Note: as object to be thrown is dynamically allocated you need to delete it otherwise you will have a memory leak.



可是throw对象的时候为啥是去看CObject的private的拷贝构造函数呢?难道不是应该调用BatchException这个类的拷贝构造函数吗?[/quote]
在affx.h文件里,CException是继承CObject的。
class AFX_NOVTABLE CException : public CObject
[/quote]

我写了一个测试类A继承测试类B,且都加上了拷贝构造方法和cout测试语句,在main函数throw A的对象实例,然后B类的拷贝构造方法测试结果显示并没有调用到.
pstrunner 2019-03-05
  • 打赏
  • 举报
回复
引用 4 楼 uouo88 的回复:
[quote=引用 3 楼 pstrunner 的回复:] 我查了下,估计这是VC的一个问题,msdn论坛解释如下:【https://social.msdn.microsoft.com/Forums/vstudio/en-US/a7dfa49c-6b81-4ce1-86a3-a926c700d626/yet-another-c2248-error?forum=vcgeneral】 The throw of an instance of a class object causes a copy to be generated when the object is copied into the C++ runtime memory location that holds the exception object. It may be possible for some compilers to "elide" (get rid of) this call to the copy-constructor in a similar way to which they can get rid of the call to a copy-constructor when returning an instance of a class object from a function (the RVO optimization) - but the compiler still needs to check that the copy-constructor is callable: so if the copy-constructor is private it should still cause an error even though the compiler would not actually generate a call to it. If you throw a pointer to an object, as in your code, then you need to catch a pointer. For example: class X { }; void f() { throw new X(); } void g() { try { f(); } catch (X* pX) { delete pX; } } Note: as object to be thrown is dynamically allocated you need to delete it otherwise you will have a memory leak.
可是throw对象的时候为啥是去看CObject的private的拷贝构造函数呢?难道不是应该调用BatchException这个类的拷贝构造函数吗?[/quote] 在affx.h文件里,CException是继承CObject的。
class AFX_NOVTABLE CException : public CObject
uouo88 2019-03-04
  • 打赏
  • 举报
回复
引用 3 楼 pstrunner 的回复:
我查了下,估计这是VC的一个问题,msdn论坛解释如下:【https://social.msdn.microsoft.com/Forums/vstudio/en-US/a7dfa49c-6b81-4ce1-86a3-a926c700d626/yet-another-c2248-error?forum=vcgeneral】

The throw of an instance of a class object causes a copy to be generated when the object is copied into the C++ runtime memory location that holds the exception object.

It may be possible for some compilers to "elide" (get rid of) this call to the copy-constructor in a similar way to which they can get rid of the call to a copy-constructor when returning an instance of a class object from a function (the RVO optimization) - but the compiler still needs to check that the copy-constructor is callable: so if the copy-constructor is private it should still cause an error even though the compiler would not actually generate a call to it.

If you throw a pointer to an object, as in your code, then you need to catch a pointer. For example:

class X { };

void f()
{
throw new X();
}

void g()
{
try {
f();
}
catch (X* pX) {
delete pX;
}
}

Note: as object to be thrown is dynamically allocated you need to delete it otherwise you will have a memory leak.



可是throw对象的时候为啥是去看CObject的private的拷贝构造函数呢?难道不是应该调用BatchException这个类的拷贝构造函数吗?
pstrunner 2019-03-04
  • 打赏
  • 举报
回复
我查了下,估计这是VC的一个问题,msdn论坛解释如下:【https://social.msdn.microsoft.com/Forums/vstudio/en-US/a7dfa49c-6b81-4ce1-86a3-a926c700d626/yet-another-c2248-error?forum=vcgeneral】 The throw of an instance of a class object causes a copy to be generated when the object is copied into the C++ runtime memory location that holds the exception object. It may be possible for some compilers to "elide" (get rid of) this call to the copy-constructor in a similar way to which they can get rid of the call to a copy-constructor when returning an instance of a class object from a function (the RVO optimization) - but the compiler still needs to check that the copy-constructor is callable: so if the copy-constructor is private it should still cause an error even though the compiler would not actually generate a call to it. If you throw a pointer to an object, as in your code, then you need to catch a pointer. For example: class X { }; void f() { throw new X(); } void g() { try { f(); } catch (X* pX) { delete pX; } } Note: as object to be thrown is dynamically allocated you need to delete it otherwise you will have a memory leak.
uouo88 2019-03-04
  • 打赏
  • 举报
回复
引用 1 楼 pstrunner 的回复:
你要看看是否存在public的CException的构造函数,我看到报错是因为private的Cobject构造函数不存在。


我看了一下,CException类的public的构造函数是存在的,但是CException继承了CObject类,CObject的拷贝构造方法是private并且标注未实现的。
然后我对比了一下VC6.0的CException和VC2017的CException,虽然都标注为抽象类,但是VC6.0的CException是可以用构造方法初始化的,但是VC2017的CException不能用它的构造方法初始化,错误提示CException是一个抽象类,不能被实例化。
pstrunner 2019-03-04
  • 打赏
  • 举报
回复
你要看看是否存在public的CException的构造函数,我看到报错是因为private的Cobject构造函数不存在。
C++面试题 1.是不是一个父类写了一个virtual 函数,如果子类覆盖它的函数不加virtual ,也能实现多态? virtual修饰符会被隐形继承的。 private 也被集成,只事派生类有访问权限而已 virtual可加可不加 子类的空间里有父类的所有变量(static除外) 同一个函数只存在一个实体(inline除外) 子类覆盖它的函数不加virtual ,也能实现多态。 在子类的空间里,有父类的私有变量。私有变量不能直接访问。 -------------------------------------------------------------------------- 2.输入一个字符串,将其逆序后输出。(使用C++,不建议用伪码) #include using namespace std; void main() { char a[50];memset(a,0,sizeof(a)); int i=0,j; char t; cin.getline(a,50,'\n'); for(i=0,j=strlen(a)-1;istr; str.replace; couta = a/9; =>a = 1; -------------------------------------------------------------------------- 5. const 符号常量; (1)const char *p (2)char const *p (3)char * const p 说明上面三种描述的区别; 如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 (1)const char *p 一个指向char类型的const对象指针,p不是常量,我们可以修改p的值,使其指向不同的char,但是不能改变它指向非char对象,如: const char *p; char c1='a'; char c2='b'; p=&c1;//ok p=&c2;//ok *p=c1;//error (2)char const *p (3)char * const p 这两个好象是一样的,此时*p可以修改,而p不能修改。 (4)const char * const p 这种是地址及指向对象都不能修改。 -------------------------------------------------------------------------- 6.下面是C语言中两种if语句判断方式。请问哪种写法更好?为什么? int n; if (n == 10) // 第一种判断方式 if (10 == n) // 第二种判断方式 如果少了个=号,编译时就会报错,减少了出错的可能行,可以检测出是否少了= -------------------------------------------------------------------------- 7.下面的代码有什么问题? void DoSomeThing(...) { char* p; ... p = malloc(1024); // 分配1K的空间 if (NULL == p) return; ... p = realloc(p, 2048); // 空间不够,重新分配到2K if (NULL == p) return; ... } A: p = malloc(1024); 应该写成: p = (char *) malloc(1024); 有释放p的空间,造成内存泄漏。 -------------------------------------------------------------------------- 8.下面的代码有什么问题?并请给出正确的写法。 void DoSomeThing(char* p) { char str[16]; int n; assert(NULL != p); sscanf(p, "%s%d", str, n); if (0 == strcmp(str, "something")) { ... } } A: sscanf(p, "%s%d", str, n); 这句该写成: sscanf(p, "%s%d", str, &n); -------------------------------------------------------------------------- 9.下面代码有什么错误? Void test1() { char string[10]; char *str1="0123456789"; strcpy(string, str1); } 数组越界 -------------------------------------------------------------------------- 10.下面代码有什么问题? Void test2() { char string[10], str1[10]; for(i=0; i<10;i++) { str1[i] ='a'; } strcpy(string, str1); } 数组越界 -------------------------------------------------------------------------- 11.下面代码有什么问题? Void test3(char* str1) { char string[10]; if(strlen(str1)<=10) { strcpy(string, str1); } } ==数组越界 ==strcpy拷贝的结束标志是查找字符串中的\0 因此如果字符串中有遇到\0的话 会一直复制,直到遇到\0,上面的123都因此产生越界的情况 建议使用 strncpy 和 memcpy -------------------------------------------------------------------------- 12.下面代码有什么问题? #define MAX_SRM 256 DSN get_SRM_no() { static int SRM_no; //是不是这里赋初值? int I; for(I=0;I=MAX_SRM) return (NULL_SRM); else return SRM_no; } 系统会初始化static int变量为0,但该值会一直保存,所谓的不可重入... -------------------------------------------------------------------------- 13.写出运行结果: {// test1 char str[] = "world"; cout << sizeof(str) << ": "; char *p = str; cout << sizeof(p) << ": "; char i = 10; cout << sizeof(i) << ": "; void *pp = malloc(10); cout << sizeof(p) << endl; } 6:4:1:4 -------------------------------------------------------------------------- 14.写出运行结果: {// test2 union V { struct X { unsigned char s1:2; unsigned char s2:3; unsigned char s3:3; } x; unsigned char c; } v; v.c = 100; printf("%d", v.x.s3); } 3 -------------------------------------------------------------------------- 15.用C++写个程序,如何判断一个操作系统是16位还是32位的?不能用sizeof()函数 A1: 16位的系统下, int i = 65536; cout << i; // 输出0; int i = 65535; cout << i; // 输出-1; 32位的系统下, int i = 65536; cout << i; // 输出65536; int i = 65535; cout <65536 ) { cout<<"32 bit"<C++有什么不同? 从机制上:c是面向过程的(但c也可以编写面向对象的程序);c++是面向对象的,提供了类。但是, c++编写面向对象的程序比c容易 从适用的方向:c适合要代码体积小的,效率高的场合,如嵌入式;c++适合更上层的,复杂的; llinux核心大部分是c写的,因为它是系统软件,效率要极高。 从名称上也可以看出,c++比c多了+,说明c++是c的超集;那为什么不叫c+而叫c++呢,是因为c++比 c来说扩充的东西太多了,所以就在c后面放上两个+;于是就成了c++ C语言是结构化编程语言,C++是面向对象编程语言。 C++侧重于对象而不是过程,侧重于类的设计而不是逻辑的设计。 -------------------------------------------------------------------------- 17.在不用第三方参数的情况下,交换两个参数的值 #include void main() { int i=60; int j=50; i=i+j; j=i-j; i=i-j; printf("i=%d\n",i); printf("j=%d\n",j); } 方法二: i^=j; j^=i; i^=j; 方法三: // 用加减实现,而且不会溢出 a = a+b-(b=a) -------------------------------------------------------------------------- 18.有关位域的面试题(为什么输出的是一个奇怪的字符) a.t = 'b';效果相当于 a.t= 'b' & 0xf; 'b' --> 01100010 'b' & 0xf -->>00000010 所以输出Ascii码为2的特殊字符 char t:4;就是4bit的字符变量,同样 unsigned short i:8;就是8bit的无符号短整形变量 -------------------------------------------------------------------------- 19.int i=10, j=10, k=3; k*=i+j; k最后的值是? 60 -------------------------------------------------------------------------- 20.进程间通信的方式有? 进程间通信的方式有 共享内存, 管道 ,Socket ,消息队列 , DDE等 -------------------------------------------------------------------------- 21. struct A { char t:4; char k:4; unsigned short i:8; unsigned long m; } sizeof(A)=?(不考虑边界对齐) 7 struct CELL // Declare CELL bit field { unsigned character : 8; // 00000000 ???????? unsigned foreground : 3; // 00000??? 00000000 unsigned intensity : 1; // 0000?000 00000000 unsigned background : 3; // 0???0000 00000000 unsigned blink : 1; // ?0000000 00000000 } screen[25][80]; // Array of bit fields 二、位结构 位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构 比按位运算符更加方便。 位结构定义的一般形式为: struct位结构名{ 数据类型 变量名: 整型常数; 数据类型 变量名: 整型常数; } 位结构变量; 其中: 数据类型必须是int(unsigned或signed)。 整型常数必须是非负的整 数, 范围是0~15, 表示二进制位的个数, 即表示有多少位。 变量名是选择项, 可以不命名, 这样规定是为了排列需要。 例如: 下面定义了一个位结构。 struct{ unsigned incon: 8; /*incon占用低字节的0~7共8位*/ unsigned txcolor: 4;/*txcolor占用高字节的0~3位共4位*/ unsigned bgcolor: 3;/*bgcolor占用高字节的4~6位共3位*/ unsigned blink: 1; /*blink占用高字节的第7位*/ }ch; 位结构成员的访问与结构成员的访问相同。 例如: 访问上例位结构中的bgcolor成员可写成: ch.bgcolor 注意: 1. 位结构中的成员可以定义为unsigned, 也可定义为signed, 但当成员长 度为1时, 会被认为是unsigned类型。因为单个位不可能具有符号。 2. 位结构中的成员不能使用数组和指针, 但位结构变量可以是数组和指针, 如果是指针, 其成员访问方式同结构指针。 3. 位结构总长度(位数), 是各个位成员定义的位数之和, 可以超过两个字 节。 4. 位结构成员可以与其它结构成员一起使用。 例如: struct info{ char name[8]; int age; struct addr address; float pay; unsigned state: 1; unsigned pay: 1; }workers; 上例的结构定义了关于一个工人的信息。其中有两个位结构成员, 每个位结 构成员只有一位, 因此只占一个字节但保存了两个信息, 该字节中第一位表示工 人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。 -------------------------------------------------------------------------- 22.下面的函数实现在一个固定的数上加上一个数,有什么错误,改正 int add_n(int n) { static int i=100; i+=n; return i; } 答: 因为static使得i的值会保留上次的值。 去掉static就可了 -------------------------------------------------------------------------- 23.下面的代码有什么问题? class A { public: A() { p=this; } ~A() { if(p!=NULL) { delete p; p=NULL; } } A* p; }; 答: 会引起无限递归 -------------------------------------------------------------------------- 24. union a { int a_int1; double a_double; int a_int2; }; typedef struct { a a1; char y; } b; class c { double c_double; b b1; a a2; }; 输出cout<VC6.0+win2k下做过试验: short - 2 int-4 float-4 double-8 指针-4 sizeof(union),以结构里面size最大的为union的size 解析C语言中的sizeof 一、sizeof的概念    sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。  二、sizeof的使用方法    1、用于数据类型    sizeof使用形式:sizeof(type)    数据类型必须用括号括住。如sizeof(int)。    2、用于变量    sizeof使用形式:sizeof(var_name)或sizeof var_name    变量名可以不用括号括住。如sizeof (var_name),sizeof var_name等都是正确形式。带括号的用法更普遍,大多数程序员采用这种形式。    注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。    如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。  三、sizeof的结果    sizeof操作符的结果类型是size_t,它在头文件 中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。    1、若操作数具有类型char、unsigned char或signed char,其结果等于1。    ANSI C正式规定字符类型为1字节。    2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、float、double、long double类型的sizeof 在ANSI C中有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。    3、当操作数是指针时,sizeof依赖于编译器。例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。一般Unix的指针字节数为4。    4、当操作数具有数组类型时,其结果是数组的总字节数。    5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。    让我们看如下结构:    struct {char b; double x;} a;    在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。    这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。    6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。  四、sizeof与其他操作符的关系    sizeof的优先级为2级,比/、%等3级运算符优先级高。它可以与其他操作符一起组成表达式。如i*sizeof(int);其中i为int类型变量。  五、sizeof的主要用途    1、sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:    void *malloc(size_t size),    size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。    2、sizeof的另一个的主要用途是计算数组中元素的个数。例如:    void * memset(void * s,int c,sizeof(s))。  六、建议    由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。 ============================================================= 本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。 1、 sizeof应用在结构上的情况 请看下面的结构: struct MyStruct { double dda1; char dda; int type }; 对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样: sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗? 其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了"对齐"处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。 类型 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量) Char 偏移量必须为sizeof(char)即1的倍数 int 偏移量必须为sizeof(int)即4的倍数 float 偏移量必须为sizeof(float)即4的倍数 double 偏移量必须为sizeof(double)即8的倍数 Short 偏移量必须为sizeof(short)即2的倍数 各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。 下面用前面的例子来说明VC到底怎么样来存放结构的。 struct MyStruct { double dda1; char dda; int type }; 为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,有放任何有意义的东西。 下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况: struct MyStruct { char dda; double dda1; int type }; 这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明) struct MyStruct { char dda;//偏移量为0,满足对齐方式,dda占用1个字节; double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8 //的倍数,需要补足7个字节才能使偏移量变为8(满足对齐 //方式),因此VC自动填充7个字节,dda1存放在偏移量为8 //的地址上,它占用8个字节。 int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍 //数,满足int的对齐方式,所以不需要VC自动填充,type存 //放在偏移量为16的地址上,它占用4个字节。 };//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构 //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof //(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为 //sizeof(double)=8的倍数。 所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,有放任何有意义的东西。 VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。 VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为n的倍数。下面举例说明其用法。 #pragma pack(push) //保存对齐状态 #pragma pack(4)//设定为4字节对齐 struct test { char m1; double m4; int m3; }; #pragma pack(pop)//恢复对齐状态 以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析) 2、 sizeof用法总结 在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。 A. 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。 B. 参数为数组或指针。下面举例说明. int a[50]; //sizeof(a)=4*50=200; 数组所占的空间大小 int *a=new int[50];// sizeof(a)=4; a为一个指针,sizeof(a)是指针 //的大小,在32位系统中,当然是占4个字节。 C. 参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。 第二、有成员变量的结构或类的大小为1,因为必须保证结构或类的每一 个实例在内存中都有唯一的地址。 下面举例说明, Class Test{int a;static double c};//sizeof(Test)=4. Test *s;//sizeof(s)=4,s为一个指针。 Class test1{ };//sizeof(test1)=1; D. 参数为其他。下面举例说明。 int func(char s[5]); { cout求按部门的字符串顺序排序,不能含有"human resource"部门, employee结构如下:employee_id, employee_name, depart_id,depart_name,wage 答: select depart_name, avg(wage) from employee where depart_name 'human resource' group by depart_name order by depart_name -------------------------------------------------------------------------- 29. 给定如下SQL数据库:Test(num INT(4)) 请用一条SQL语句返回num的最小值,但不许使用统计功能,如MIN,MAX等 答: select top 1 num from Test order by num desc -------------------------------------------------------------------------- 30. 输出下面程序结果。 #include class A { public: virtual void print(void) { cout<<"A::print()"<print(); pc->print(); print(a); print(b); print(c); } A: A::print() B::print() C::print() A::print() B::print() C::print() A::print() A::print() A::print() -------------------------------------------------------------------------- 31. 试编写函数判断计算机的字节存储顺序是开序(little endian)还是降序(bigendian) 答: bool IsBigendian() { unsigned short usData = 0x1122; unsigned char *pucData = (unsigned char*)&usData; return (*pucData == 0x22); } -------------------------------------------------------------------------- 32.简述Critical Section和Mutex的不同点 答: 对几种同步对象的总结 1.Critical Section A.速度快 B.不能用于不同进程 C.不能进行资源统计(每次只可以有一个线程对共享资源进行存取) 2.Mutex A.速度慢 B.可用于不同进程 C.不能进行资源统计 3.Semaphore A.速度慢 B.可用于不同进程 C.可进行资源统计(可以让一个或超过一个线程对共享资源进行存取) 4.Event A.速度慢 B.可用于不同进程 C.可进行资源统计 -------------------------------------------------------------------------- 33.一个数据库中有两个表: 一张表为Customer,含字段ID,Name; 一张表为Order,含字段ID,CustomerID(连向Customer中ID的外键),Revenue; 写出每个Customer的Revenue总和的SQL语句。 建表 create table customer ( ID int primary key,Name char(10) ) go create table [order] ( ID int primary key,CustomerID int foreign key references customer(id) , Revenue float ) go --查询 select Customer.ID, sum( isnull([Order].Revenue,0) ) from customer full join [order] on( [order].customerid=customer.id ) group by customer.id -------------------------------------------------------------------------- 34.请指出下列程序中的错误并且修改 void GetMemory(char *p){ p=(char *)malloc(100); } void Test(void){ char *str=NULL; GetMemory=(str); strcpy(str,"hello world"); printf(str); } A:错误--参数的值改变后,不会传回 GetMemory并不能传递动态内存,Test函数中的 str一直都是 NULL。 strcpy(str, "hello world");将使程序崩溃。 修改如下: char *GetMemory(){ char *p=(char *)malloc(100); return p; } void Test(void){ char *str=NULL; str=GetMemory(){ strcpy(str,"hello world"); printf(str); } 方法二:void GetMemory2(char **p)变为二级指针. void GetMemory2(char **p, int num) { *p = (char *)malloc(sizeof(char) * num); } -------------------------------------------------------------------------- 35.程序改错 class mml { private: static unsigned int x; public: mml(){ x++; } mml(static unsigned int &) {x++;} ~mml{x--;} pulic: virtual mon() {} = 0; static unsigned int mmc(){return x;} ...... }; class nnl:public mml { private: static unsigned int y; public: nnl(){ x++; } nnl(static unsigned int &) {x++;} ~nnl{x--;} public: virtual mon() {}; static unsigned int nnc(){return y;} ...... }; 代码片断: mml* pp = new nnl; .......... delete pp; A: 基类的析构函数应该为虚函数 virtual ~mml{x--;} -------------------------------------------------------------------------- 36.101个硬币100真、1假,真假区别在于重量。请用无砝码天平称两次给出真币重还是假币重的结论。 答: 101个先取出2堆, 33,33 第一次称,如果不相等,说明有一堆重或轻 那么把重的那堆拿下来,再放另外35个中的33 如果相等,说明假的重,如果不相等,新放上去的还是重的话,说明假的轻(不可能新放上去的轻) 第一次称,如果相等的话,这66个肯定都是真的,从这66个中取出35个来,与剩下的称过的35个比 下面就不用说了 方法二: 第3题也可以拿A(50),B(50)比一下,一样的话拿剩下的一个和真的比一下。 如果不一样,就拿其中的一堆。比如A(50)再分成两堆25比一下,一样的话就在 B(50)中,不一样就在A(50)中,结合第一次的结果就知道了。 -------------------------------------------------------------------------- 37.static变量和static 函数各有什么特点? 答: static变量:在程序运行期内一直有效,如果定义在函数外,则在编译单元内可见,如果在函数内,在在定义的block内可见; static函数:在编译单元内可见; -------------------------------------------------------------------------- 38.用C 写一个输入的整数,倒着输出整数的函数,要用递归方法 ; 答: void fun( int a ) { printf( "%d", a%10 ); a /= 10; if( a <=0 )return; fun( a ); } -------------------------------------------------------------------------- 39.写出程序结果: void Func(char str[100]) { printf("%d\n", sizeof(str)); } 答: 4 分析: 指针长度 -------------------------------------------------------------------------- 40.int id[sizeof(unsigned long)]; 这个对吗?为什么?? 答: 对 这个 sizeof是编译时运算符,编译时就确定了 可以看成和机器有关的常量。 本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。 1、 sizeof应用在结构上的情况 请看下面的结构: struct MyStruct { double dda1; char dda; int type }; 对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样: sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗? 其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了"对齐"处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。 类型 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量) Char 偏移量必须为sizeof(char)即1的倍数 int 偏移量必须为sizeof(int)即4的倍数 float 偏移量必须为sizeof(float)即4的倍数 double 偏移量必须为sizeof(double)即8的倍数 Short 偏移量必须为sizeof(short)即2的倍数 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ljzcome/archive/2006/01/09/574158.aspx
摘自:http://mbstudio.spaces.live.com/blog/cns!C898C3C40396DC11!955.entry 2007/1/30 oSIP协议栈(及eXoSIP,Ortp等)使用入门(原创更新中) (CopyLeft by Meineson | www.mbstudio.cn,原创文章,欢迎转载,但请保留出处说明!) 本文档最新版本及文中提到的相关源码及VC6工程文件请在本站找,嘿嘿~~ (首页的SkyDriver公开文件夹中,可能需要用代理才能正常访问该空间——空间绝对稳定,不会丢失文件!) (最近工作重心不在SIP开发,SO本文档也有机会更新,有技术问题也请尽量咨询他人,本人不一定能及时回复。)   一直空仔细研究下oSIP,最近看到其版本已经到了3.x版本,看到网上的许多帮助说明手册都过于陈旧,且很多文档内容有点误人子弟的嫌疑~~   Linux下oSIP的编译使用应该是很简单的,其Install说明文档里也介绍的比较清楚,本文主要就oSIP在Windows平台下VC6.0开发环境下的使用作出描述。   虽然oSIP的开发人员也说明了,oSIP只使用了标准C开发库,但许多人在Windows下使用oSIP时,第一步就被卡住了,得不到oSIP的LIB库和DLL库,也就有办法将oSIP使用到自己的程序中去,所以第一步,我们将学习如何得到oSIP的静态和动态链接库,以便我们自己的程序能够使用它们来成功编译和执行我们的程序。 第一阶段: ------------------------------------------------------   先创建新工程,网上许多文档都介绍创建一个Win32动态链接库工程,我们这里也一样,创建一个空白的工程保存。   同样,将oSIP2版本3.0.1 src目录下的Osipparser2目录下的所有文件都拷到我们刚创建的工程的根目录下,在VC6上操作: Project-Add To Project-Files   将所有的源程序和头文件都加入到工程内,保存工程。   这时,我们可以尝试编译一下工程,你会得到许多错误提示信息,其内容无非是找不到osipparser2/xxxxx.h头文件之类。   处理:在Linux下,我们一般是将头文件,lib库都拷到/usr/inclue;/usr/lib之类的目录下,c源程序里直接写#include 时,能直接去找到它们,在VC里,同样的,最简单的方法就是将oSIP2源码包中的Include目录下的 osipparser2目录直接拷到我们的Windows下默认包含目录即可,这个目录在VC6的Tool-Options-Directories里设置,(当然,如果你知道这一步,也可以不用拷贝文件,直接在这里把oSIP源码包所在目录加进来就可以了),默认如果装在C盘,目录则为 C:\Program Files\Microsoft Visual Studio\VC98\Include。   这时,我们再次编译我们的工程,顺利编译,生成osipparser2.dll,这时,网上很多文档里可能直接就说,这一步也会生成libs目录,里面里osipparser2.lib文件,但我们这里有生成:)   最简单的方法,不用深究,直接再创建一个工程,同上述创建动态链接库方法,创建一个Win32静态链接库工程,直接编译,即可得到osipparser2.lib。 ------------------------------------------------------   上面,我们得到了Osip的解析器开发库,下面再编译完整的Osip协议栈开发库,同样照上述方法,分别创建动态链接库工程和静态链接库工程,只是要拷的文件换成src下的osip目录下文件和include下的osip目录,得到osip2.dll和osip2.lib。   在编译osip2.dll这一步可能会再次得到错误,内容含义是找不到链接库,所以,我们要把前面编译得到的osipparser2.lib也拷到osip工程目录下,并在VC6中操作:   Project-Setting-Link中的Object/Library Modules: kernel32.lib user32.lib ... xxx.lib之类的内容最后增加: osipparser2.lib   保存工程后再次编译,即可成功编译osip2.dll。 ------------------------------------------------------   至此,我们得到了完整的oSIP开发库,使用时,只需在我们的程序里包含oSIP的头文件,工程的链接参数里增加osipparser2.lib和osip2.lib即可。 ------------------------------------------------------   下面我们验证一下我们得到的开发库,并大概了解一下OSIP的语法规范。   在VC里创建win32控制台程序工程,将libosip源码包的SRC目录下的Test目录内的C源程序随便拷一个到工程时,直接编译(工程设置里照前文方法在link选项里增加osip2.lib,osipparser2.lib引用我们之前成功编译得到的静态库文件)就可以运行(带参数运行,参数一般为一个文本文件,同样从Test目录的res目录里拷一个与源文件同名的纯文本文件到工程目录下即可)。   该目录下的若干文件基本上是测试了Osip的一些基本功能函数,例如URI解析之类,可以大概了解一下oSIP的语法规范和调用方法,同时也能校验一下之前编译的OSIP开发库能否正常使用,成功完成本项工作后,可以进入下一步具体的oSIP的使用学习了。 ------------------------------------------------------   由于oSIP是比较底层的SIP协议栈实现,新手较难上手,而官方的示例大都是一些伪代码,需要有实际的例子程序参考学习,而最好的例子就是同样官方发布的oSIP的扩展开发库exosip2,使用exoSIP可以很方便地快速创建一个完整的SIP程序(只针对性地适用于SIP终端开发用,所以我们这里只是用它快速开发一个SIP终端,用来更方便地学习oSIP,要想真正掌握SIP的开发,需要掌握oSIP并熟读RFC文档才行,exoSIP不是我们的最终学习目的),通过成功编译运行一个自己动手开发出的程序,再由浅入深应该是初学都最好的学习方法通过对使用exosip开发库的使用创建自己的SIP程序,熟悉后再一个函数一个函数地深入学习exosip提供的接口函数,就可以深入理解osip 了,达到间接学习oSIP的目的,同时也能从eXoSIP中学习到正确使用oSIP的良好的编程风格和语法格式。   而要成功编译ExoSIP,似乎许多人被难住了,直接在XP-sp2上,用VC6,虽然你使用了eXoSIP推荐的winsock2.h,但是会得到一个 sockaddr_storage结构不能识别的错误,因为vc6自带的开发库太古董了,需要升级系统的Platform SDK,下载地址如下: http://www.microsoft.com/msdownl ... PSP2FULLInstall.htm(VC6的支持已经停止,这是VC6能使用的最新SDK)   成功安装后编译前需加OSIP_MT宏,以启用线程库,否则在程序中使用eXoSIP库时会出错,而编译时也会得到许多函数未定义的Warning提示,编译得到exosip2.lib供我们使用,当然,在此之前需要成功编译了osip2和osipparser2,而在之后的实际使用时,发现oSIP也需要增加OSIP_MT宏,否则OSIP_MT调用oSIP的线程库时会出错,所以我们需要重新编译oSIP了:),因为eXosip是基于oSIP的(同上方式创建静态和动态链接库工程,并需在Link中手工添加oSIP和oSIPparser的lib库)。 ------------------------------------------------------   创建新工程,可以是任意工程,我们从最简单的Win32控制台程序开始,为了成功使用oSIP,我们需要引用相关库,调用相关头文件,经过多次试验,发现需要引用如下的库: exosip2.lib osip2.lib osipparser2.lib WSock32.Lib IPHlpApi.Lib WS2_32.Lib Dnsapi.lib   其中,除了我们上面编译得到的三个oSIP库外,其它库都是系统库,其中有一些是新安装的Platform SDK所新提供的。   至此,我们有了一个简单的开发环境了,可以充分利用网上大量的以oSIP为基础的代码片段和官方说明文档开始具体函数功能的测试和使用了:) ------------------------------------------------------   我们先进行一个简单的纯SIP信令(不带语音连接建立)的UAC的SIP终端的程序开发的试验(即一个只能作为主叫不能作为被叫的的SIP软电话模型),我们创建一个MFC应用程序,对话框模式,照上面的说明,设置工程包含我们上面得到的oSIP的相关开发库及SDK的一些开发库,并且由于默认LIBC的冲突,需要排除MSVCRT[D]开发库(其中D代表Debug模式下,有D表示Release模式下),直接使用eXosip的几个主要函数就可以创建一个基本的SIP软电话模型。   其主要流程为:   初始化eXosip库-启动事件监听线程-向SIP Proxy注册-向某SIP终端(电话号码)发起呼叫-建立连接-结束连接   初始化代码: int ret = 0; ret = eXosip_init (); eXosip_set_user_agent("##YouToo0.1"); if(0 != ret) { AfxMessageBox("Couldn't initialize eXosip!\n"); return false; } ret = eXosip_listen_addr (IPPROTO_UDP, NULL, 0, AF_INET, 0); if(0 != ret) { eXosip_quit (); AfxMessageBox("Couldn't initialize transport layer!\n"); return false; }   启动事件监听线程: AfxBeginThread(sip_uac,(void *)this);   向SIP Proxy注册: eXosip_clear_authentication_info(); eXosip_add_authentication_info(uname, uname, upwd, "md5", NULL); real_send_register(30);  /* 自定义函数代码请见源码 */   发起呼叫(构建假的SDP描述,实际软电话使用它构建RTP媒体连接): osip_message_t *invite = NULL; /* 呼叫发起消息体 */ int i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "## YouToo test demo!"); if (i != 0) { AfxMessageBox("Intial INVITE failed!\n"); } char localip[128]; eXosip_guess_localip (AF_INET, localip, 128); snprintf (tmp, 4096, "v=0\r\n" "o=josua 0 0 IN IP4 %s\r\n" "s=conversation\r\n" "c=IN IP4 %s\r\n" "t=0 0\r\n" "m=audio %s RTP/AVP 0 8 101\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:101 telephone-event/8000\r\n" "a=fmtp:101 0-11\r\n", localip, localip, "9900"); osip_message_set_body (invite, tmp, strlen(tmp)); osip_message_set_content_type (invite, "application/sdp"); eXosip_lock (); i = eXosip_call_send_initial_invite (invite); eXosip_unlock ();   挂断或取消通话: int ret; ret = eXosip_call_terminate(call_id, dialog_id); if(0 != ret) { AfxMessageBox("hangup/terminate Failed!"); }   可以看到非常简单,再借助于oRTP和Mediastreamer开发库,来快速为我们的SIP软电话增加RTP和与系统语音API接口交互及语音编码功能,即可以快速开发出一个可用的SIP软电话,关于oRTP和Mediastreamer的相关介绍不是本文重点,将在有空的时候考虑增加相应使用教程,文章前提到的地方可以下载基本可用的完整SIP软电话的VC源码工程文件供参考使用,完全CopyLeft,欢迎转载,但请在转载时注明作者信息,谢谢! 第二阶段: ---------------------------------------------------   得到了一个SIP软电话模型后,我们可以根据软电话的实际运行表现(结合用Ethereal抓包分析)来进行代码分析,以达到利用eXoSIP来辅助我们学习oSIP的最终目的(如要快速开发一个可用的SIP软电话,请至前面提到的论坛去下载使用oRTP和Mediastreamer快速搭建的一个基本完整可用的SIP软电话##YouToo 0.1版本的VC源码工程文件作参考)。   现在从eXosip的初始化函数开始入手,来分析oSIP的使用,这是第二阶段,第三阶段就是深入学习oSIP的源码了,但大多数情况下应该有必要了,因为在第二阶段就有部分涉及到第三阶段的工作了,而且oSIP的源码也就大多是一些SIP数据的语法解析和状态机的实现,能深入理解了SIP协议后,这些只是一种实现方式,必要完全去接受,而是可以用自己的方式和风格来实现一套,比如,更轻量化更有适用目的性的方式,oSIP则只起参考作用了。   eXosip_init()是eXosip的初始化函数,我们来看看它的内部实现:   首行是定义的 osip_t *osip,这在oSIP的官方手册里我们看到,所有使用oSIP的程序都要在最开始处声明一个osip_t的指针,并使用 osip_init(&osip)来初始化这个指针,销毁这个资源使用osip_release(osip)即可。   我们可以在代码中看到很多OSIP_TRACE,这是调试输出宏调用了函数osip_trace,可以用ENABLE_TRACE宏来打开调试以方便我们开发调试。   其它就是很多的eXosip_t的全局变量eXosip的一些初始化操作,包括最上面的memset (&eXosip, 0, sizeof (eXosip))完全清空和下面的类似eXosip.user_agent = osip_strdup ("eXosip/" EXOSIP_VERSION)的exosip变量的一些初始值设置,其中有一个eXosip.j_stop_ua = 0应该是一个状态机开关,后面可以看到很多代码检测这个变量来决定是否继续流程处理,默认置成了0表示现在exosip的处理流程是就绪的,即ua是 not stop的。      osip_set_application_context (osip, &eXosip)是比较有意思的,它让下面的eXosip_set_callbacks (osip)给osip设置大量的回调函数时,能让osip能访问到eXosip这个全局变量中设置的大量程序运行时交互的信息,相当于我们在VC下开启一个线程时,给线程传入的一个void指针指向我们的MFC应用程序的当前dialog对象实例,可以用void *osip_get_application_context (osip_t * osip)这个函数来取出指针来使用,不过好象exosip中并有用到它,可能是留给个人自已扩展的吧:)      还能看到初始化代码前面有一段WIN32平台下的SOCK的初始化代码,可以知道eXosip是用的原生的winsock api函数,也就是我们可能以前学过的用VC和WINAPI写sock程序时(不是MFC),用到的那段SOCK初始代码,还有一段有意思的代码,就是 jpipe()函数,它们返回的是一个管道,一个有2个整型数值的数组(一个进一个出),查看其代码发现,非WIN32平台是直接使用的pipe系统函数,而WIN32下则是用一对TCP的本地SOCK连接来模拟的管道,一个SOCK写一个SOCK读,这段代码是比较有参考价值的:) j = 50; while (aport++ && j-- > 0) {   raddr.sin_port = htons ((short) aport);   if (bind (s, (struct sockaddr *) &raddr, sizeof (raddr)) transactionid)); }   即,只是打印一下调试,并有完整实现什么功能,我们学习时,完全可以用相同的方法,定义一大堆回调函数,并不忙想怎么完全实现,先都是只打印一下调试信息,看具体的应用逻辑根据抓包测试分析和看调试看程序走到了哪一步,调用了哪一个回调,来明白具体回调函数要实现什么用途,再来实现代码就方便多了,当然,如果看透了RFC文档,应该从字面就能知道各个回调函数的用途了,这是后话,不是谁都能快速完全看懂RFC的,所以我们要参考eXosip:)      我们对其中的重要的回调函数进行逐个的分析:   ---------------------------   osip_set_cb_send_message (osip, &cb_snd_message) SIP消息发送回调函数   这个函数可能是最重要的回调函数之一,消息发送,包括请消息和回应消息,一般情况下,状态机的状态就是由它控制的,发起一个消息初始化一个状态机,回应一个消息对状态机修改,终结消息发送结束状态机……   看cb_snd_message的函数实现,要以发现,其主要代码是对参数中的要发送的消息osip_message_t * sip进行分析,找出消息要发送的真实char *host,int port的值(这些参数可以省略,但要发送消息肯定需要host和port,所以要从sip中解析),最后根据sip中解析出的传输方式是TCP还是 UDP选择最终进行消息发送处理的函数cb_udp_snd_message,cb_tcp_snd_message处理(它们的参数一致,即本函数只是补全一些省略的参数并对消息进行合法性检查)。   **毕竟eXosip是一个通用的开发库,它考虑了要支持TCP,UDP,TCPs,IPV4,IPV6,WIN32,*nix,WINCE等等多样化的复杂环境,所以,我们可以略过我们暂时不需要的部分,比如,IPV6相关的代码实现等。      由于我们大多数情况下SIP是用的UDP,所以先来看一下cb_udp_snd_message的实现,它从全局变量exosip中获取可用的 sock,并尽最大能力解析出host和port(??难道前面的函数还不够解析彻底??如最终仍无port信息则默认设置为5060),使用 osip_message_to_str (sip, &message, &length)函数将要发送的格式化的SIP消息转换成能用SOCK传输的简单数据并发送即完成消息发送,代码中有许多复杂的环境探测和错误控制等等等等,我们可以暂时不用过多关注,可以继续向下,结尾处有一个keeplive相关代码,从代码字面分析,可能是SIP的Register消息的自动重发相关代码,可以在后面再细化分析。   cb_tcp_snd_essage的函数实现要比上文的udp的实现简单很多,主要是环境探测错误控制方面,因为毕竟tcp是稳定连接的,对比一下代码,可以看到主要流程还是将SIP消息转换后,发送到从SIP消息中解析出的host和port对应的目标。      看完两个函数,可以知道,eXosip需要有两个sock,是一个数组,0是给UDP用的,1是给TCP用的,要用SOCK当然要初始化,就是下文要介绍的eXosip的网络相关的初始化了,上面的exosip_init可以看成是这个开发库的系统初始化吧:)    至些,我们应该知道了oSIP开发的SIP应用程序的消息是从哪里发出的吧,对了,就是从这个回调函数里,所谓万事开头难,就象开发WIN32应用程序时,找到了WIN32程序的main函数入口下面的工作就好办了,下面就都是为一些事件消息开发对应的处理函数而已了:)   osip_set_kill_transaction_callback 事务终结回调函数   对应ICT,IST,NICT,NIST客户/服务器注册/非注册事务状态机的终结,主要是使用osip_remove_transaction (eXosip.j_osip, tr)将当前tr事务删除,再加上一系列的清理工作,其中,NICT即客户端的非Invite事务的清理比较复杂一些,要处理的内容也比较多,可以根据实际应用的情况进行有必要的清理工作:)   cb_transport_error 传输失败处理回调   对应于上面说到的四种事务状态机,如果它们在处理时失败,则在这时进行统一处理。   从代码可知,只是在NOTIFY,SUBSCRIBE,OPTION操作失败才进行处理,其它错误可直接忽略。   osip_set_message_callback 消息发送处理回调   根据type不同,表示不同的消息发送状态   OSIP_XXX_AGAIN 重发相关消息   OSIP_ICT_INVITE_SENT 发起呼叫   OSIP_ICT_ACK_SENT ACK回应   OSIP_NICT_REGISTER_SENT 发起注册   OSIP_NICT_BYE_SENT BYE发出   OSIP_NICT_CANCEL_SENT Cancel发出   OSIP_NICT_INFO_SENT,OSIP_NICT_OPTIONS_SENT,OSIP_NICT_SUBSCRIBE_SENT,OSIP_NICT_NOTIFY_SENT,OSIP_NICT_UNKNOWN_REQUEST_SENT   我们可以看到,eXosip有对它们作任何处理,我们可以根据自己需要,比如,重发2xx消息前记录一下日志之类的,扩展一下retransmission的处理方式,发起Invite前记录一下通话日志等等。   OSIP_ICT_STATUS_1XX_RECEIVED uac收到1xx消息,一般是表示对端正在处理中,这时,主要是设置一下事务状态机的状态值,并对会话中的osip的一些参数根据返回值进行相应设置,里面有许多条件判断,但我们常用的一般是100,180,183的判断而已,暂时可以忽略里面复杂的判断代码。   OSIP_ICT_STATUS_2XX_RECEIVED uac收到2xx消息,这里主要跟踪一下Register情况下的2xx,表示注册成功,这时会更新一下exosip的注册字段值,以便让eXosip能自动维护uac的注册,BYE的2xx回应是终结消息,Invite的2xx回应,则主要是初始化一下会话相关的数据,表示已成功建立连接。   其它4xx,5xx,6xx则分别是对应的处理,根据实现情况进行概要的查看即可。   report_event (je, sip)是代码中用来进行事件处理的一个函数,跟踪后发现,其最终是使用了我们上文提到的jpipe管道,以便在状态机外实时观测状态机内的处理信息。      OSIP_NIST_STATUS_XXX_SENT即对应于上面的uac的处理,这里是uas的对应的消息处理,相比较于uac简单一点。   前面简单介绍了一下大量的回调函数及它们的概要处理逻辑,可能会比较混乱,暂时不用管它,只需要记得一个大概的形象,知道一个SIP处理程序是通过osip_set_cb_send_message回调函数来实现真实地发送各种SIP消息,并且SIP的标准事务模型是由oSIP实现好了,我们只需要给不同的事务状态设置不同的回调处理函数来处理事务,具体的状态变化和内部逻辑不用管就可以了。   下面来说一下消息处理回调函数用到的SOCK的初始化函数,即我们上面说的除了系统初始化外的网络初始化函数eXosip_listen_addr:   从上文知道了,系统将初始化两个SOCK,一个UDP一个TCP,但查看代码发现还有第三个,TCPs的,但好象还不能实用,现在不管它,代码首先是根据传输是UDP还是TCP来设置对应的数组值,并且如果有提供IP地址和端口号,系统会自动取出本机网络接口并创建可用的SOCK(http_port 的方式暂不用考虑)。   SOCK初始化后,如何开始SIP事务的呢?看到这个调用eXosip.j_thread = (void *) osip_thread_create (20000, _eXosip_thread, NULL),对的,这里启用了一个线程,即,eXosip是调用oSIP的线程函数(用系统提供的线程函数,是为了跨平台)进行事务处理的状态机逻辑是在一个线程中处理的,这样就明白了为什么一直能看到顺序执行下来的程序启动代码了,接下去看,线程实际处理函数是_eXosip_thread,这里面的代码中,我们看到了上文提到的状态机控制开关变量while (eXosip.j_stop_ua == 0),即,当j_stop_ua设置为1时,osip_thread_exit ()结束事务处理即程序终结,再接下去看,_eXosip_execute是最终的处理函数了,而且它在程序未终结情况下是一直逻辑在执行,注意,要启用oSIP的多线程宏OSIP_MT。      看到_eXosip_execute的代码中有很多时间函数和变量,仔细看,除去一些控制代码,主要处理函数是eXosip_read_message (1, lower_tv.tv_sec, lower_tv.tv_usec),即取出消息,1表示只取出一条消息,其代码量非常的大,但同样的,其中也许多的控制代码和错误检测代码,我们在查看时可以暂时忽略掉它们。   eXosip_read_message读取消息时,即有采用sock的block也有用非block方式,而是采用了select方式,具体应用可查询fd_set相关文档。   根据jpipe_read (eXosip.j_socketctl, buf2, 499),我们可以估计,buf2中应该是保存的我们的控制管道的数据,具体作用至些还有表现出来,应该是用来反映一些状态机内部的警示之类的信息,实际的SIP的处理的状态机的数据是存放在buf中,使用_eXosip_recvfrom获取的,获取后sipevent = osip_parse (buf, i)解析,使用osip_find_transaction_and_add_event (eXosip.j_osip, sipevent)来查询事件对应的事务状态机,找到后就如同其注解所说明的,/* handled by oSIP ! */,即我们上文设置的那一大堆回调函数,至此,我们知道了整个SIP应用所处理的大概流程了。   如果有找到事务状态机呢?直接丢弃吗?不是的,如果这是一个回应消息,但有事务状态机处理它,那它是一个错误的,要进行清理后才能丢弃,而如果是一个请,那更不能丢弃了,因为UAS事务状态机要由它来启动创建的(回应消息表示本地发出了请消息,即UAC行为,事务状态机应是由启动UAC的代码初始化启动的),整个逻辑应该是很简单的,但eXosip的实现代码却非常多,可见其花了非常多的精力在保证会话的稳定性和应付网络复杂情况上,我们可以对其进行大量的精简来构建满足我们需代码实现。   先来看错误的回应消息的处理函数eXosip_process_response_out_of_transaction,可以看到其代码就是一大堆的赋值语句,XXX= NULL,即将一大堆的运行时变量清空,再调用osip_event_free清空事件,或者就是一些复杂的情况下,需要通过解析现在的运行时数据,从中分析出“可能”的正在等待回应的对端,并发送相关终结通知消息等等,可以根据实际需要进行简化。   请事件的处理 eXosip_process_newrequest,首先是对事件进行探测,MSG_IS_INVITE、MSG_IS_ACK、 MSG_IS_REQUEST……,对事件进行所属状态机分类,随后使用_eXosip_transaction_init (&transaction,(osip_fsm_type_t) tx_type,eXosip.j_osip, evt->sip)根据探测结果进行状态机初始化,实际调用的是osip_transaction_init,初始化后即将事件入状态机 osip_transaction_add_event (transaction, evt),由状态机自动处理后调用相应回调函数处理逻辑了。当然,eXosip为方便快速开发SIP终端应用,在下面又添加了许多自动化的处理代码,来和我们在回调函数中设置的处理代码相区分。   线程调用的事件处理函数代码最后是 if (eXosip.keep_alive > 0) {   _eXosip_keep_alive (); }   这段代码印证了上文提到了,keep_alive是用来设置是否自动重新注册,由_eXosip_keep_alive函数来实现自动将eXosip全局变量中保存的注册消息解析后自动根据需要重新向SIP服务器发起Register注册。   同样,因为注册消息发起是UAC的行为,将它放在这里,可以看出来所有事件消息的事务状态机处理都是在这里,只不过这里只创建UAS的事务状态机,UAC的事务状态机的创建则要继续到下面找了,从我们的YouToo软电话代码中可知,发起呼叫和发起注册分别调用了 eXosip_call_send_initial_invite,eXosip_register_send_register这两个函数(另外用到的两个build函数则是分别构建这两个send函数要发送的SIP消息),查看这两个函数可知,UAC的事务处理状态机是在这里进行初始化的。   eXosip_register_send_register中可以看到是_eXosip_transaction_init (&transaction, NICT, eXosip.j_osip, reg)初始化UAC状态机,实际也同UAS是调用的osip_transaction_init函数,同样使用 osip_transaction_add_event (transaction, sipevent)将事件入状态机,状态机随后将自动处理调用相应回调函数处理逻辑了。   另有osip_new_outgoing_sipmessage(reg),表示发送消息,到这里,我们应该可以理解,真实的发送操作,是要到由状态机处理后,调用了消息发送回调函数才真正地将注册消息发送出去的。   同注册消息发送,它是NICT状态机,呼叫消息的发送是ICT,由eXosip_call_send_initial_invite处理,_eXosip_transaction_init (&transaction, ICT, eXosip.j_osip, invite)初始化了状态机,之前还有一个eXosip_call_init是用来初始化eXosip的一些参数的,暂时不管它,同样 osip_new_outgoing_sipmessage (invite)发送呼叫消息,但实际还是要状态机处理后调用消息发送回调函数真实发送呼叫请函数的,osip_transaction_add_event (transaction, sipevent)则标准地,将事件入状态机,状态机将能处理随后的应用逻辑调用相应的回调函数了。   好了,作了这么多的分析,我们了解了eXosip是怎样调用oSIP来形成被我能方便地再次调用的了,可以看到,为了实现最大限度的跨平台和兼容性,代码中有大量的测试代码,宏定义和错误再处理代码,看起来非常吃力,但了解了其主要的调用框架:   初始化,回调函数设置,UAC和UAS事务处理状态机的启动,事件处理流程等,就可以基本明白了oSIP各个函数的主要作用和正确的用法了,下一步,可以参考eXosip来针对某个应用,去除掉大量暂时用不到的代码,来构建一个简单的SIP软电话和SIP服务器,来进一步深入oSIP学习应用了。  ------------------------------------------------------ [下回预告:完全基于oSIP的软电话实现及oSIP进一步学习] (CopyLeft by Meineson | www.mbstudio.cn,原创文章,欢迎转载,但请保留出处说明!) 附件为原作者提供的

65,199

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧