一直不明白c++中类中的函数和成员变量在实例化对象后在内存中究竟是个什么样的布局。一度以为一个对象的内存布局应该包括函数体和成员变量,后来才知道对象的内存布局是不包括函数体的。也就是说实例化一个类后,该对象占用的内存空间大小实际上是它的成员变量在内存中所占用空间大小(注意,含有静态成员变量和虚函数的类对象例外)。以下分析以下c++类对象在内存中的布局,并且探讨在给定一个类的成员变量的类型和数量时,如何使得类实例化后对象所占用的内存空间最小(考虑变量在内存中对齐)。
先给出一个类,如下:
1 class A
2 {
3 public:
4 A()
5 {
6 cout << "A" << endl;
7 }
8 virtual ~A()
9 {
10 cout << "~A" << endl;
11 }
12 virtual void printA()
13 {
14 cout << "printA" << endl;
15 }
16 private:
17
18 int a;
19 char c;
20 char e;
21 double b;
22 static double d;
23 };
24
25 int main()
26 {
27
28 A a;
29 cout << sizeof(a) << endl; //输出是24
30 return 0;
31 }
以上代码输出是:24。也就是说对象a占用内存空间大小是24字节,这个24字节是怎么得来的呢?我们注意到在类A中存在虚函数,而只有类中有虚函数存在,则可以知道,在每个类对象占用内存空间的首部都会有一个虚函数表,这个虚函数表可以看成是一个指针数组,在对象a中虚函数表中总共有2项,因为类A中存在两个虚函数,每个虚函数都在虚函数表中有一个项对应。现在我们知道虚函数表是2个指针的内存大小,所以是2*4=8字节。然后a,c,e总共占用4+1+1=6字节,由于需要进行内存对齐,所以实际上它占用的大小是8字节(即类对象中的内存按照占用内存空间最大的变量来对齐,static变量不考虑在内)。然后是b占用8字节,static变量d不占用空间,综上对象a占用空间大小为8(虚函数表)+8(a,c,e)+8(b)=24字节。具体可以在vs下调试,查看a的地址。
由于要考虑到内存对齐,所以。。今天有事,以后待续
1.容器在销毁时,会自动帮你销毁容器中所存储的额对象。
2.当容器中对象是指针的时候,容器销毁的时候,也会帮你销毁容器中的对象-指针。
3.由于容器销毁销毁的只是指针值,而非指针所指对象。则会出现资源泄露
解决方法:
1.用智能指针auto_ptr,shared_ptr来代替指针。思想:用类对象来管理指针的创建销毁。构造函数创建指针,析构函数销毁指针所指对象。
2.显示销毁指针所指对象,使用for_each,结合函数对象。可以实现异常安全
注:刚看了<<effective stl>>条款8,里面说如果使用auto_ptr作为容器对象是愚蠢的行为。所以上面的方法1,用shared_ptr而不能用auto_ptr。
主要是因为auto_ptr在拷贝的时候有一个所有权的转移。
UML类图中的“关联关系(association)”、“聚合关系(aggregation)"、”合成关系(composition"和“依赖关系(depedency)。
(1)关联关系 (association): (1)关联关系是类与类之间的联结,它使一个类知道另一个类的属性和方法。
(2)关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。
1、聚合关系是关联关系的一种,是强的关联关系。
2、聚合是整体和部分之间的关系,例如汽车由引擎、轮胎以及其它零件组成。
3、聚合关系也是通过成员变量来实现的。但是,关联关系所涉及的两个类处在同一个层次上,而聚合关系 中,两个类处于不同的层次上,一个代表整体,一个代表部分。
4、关联与聚合仅仅从 Java 或 C++ 语法上是无法分辨的,必须考察所涉及的类之间的逻辑关系。
(3)合成关系 (composition): 1、合成关系是关联关系的一种,是比聚合关系还要强的关系。
2、它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。
(4)依赖关系 (dependency): 1、依赖关系也是类与类之间的联结
2、依赖总是单向的。
3、依赖关系在 Java 或 C++ 语言中体现为局部变量、方法的参数或者对静态方法的调用。
需要准备的知识点:
1. linux网络编程,多线程,僵尸进程
2. 数据库范式和基本sql语句
3. 项目
4. Select,poll
5. 在fork前加上信号处理函数:
signal(SIG_CHLD, Proc_CHLD);
void Proc_CHLD(int SIGNO)
{
int pid = -1;
int stat;
while(pid=waitpid(0, &stat, WHNONG);
}
6. MFC消息机制,文档视图模型,生死之谜
7. Pthread_join是等待线程结束。Wait和waitpid是获取子进程的退出码,防止僵尸进程长期存在
8. .僵尸进程解决办法:
4.1 改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行 waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,尽管对的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
4.2 把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。
kill -9 `ps -ef | grep "Process Name" | awk '{ print $3 }'`
其中,“Process Name”为处于zombie状态的进程名。
4.3 杀父进程不行的话,就尝试用skill -t TTY关闭相应终端,TTY是进程相应的tty号(终端号)。但是,ps可能会查不到特定进程的tty号,这时就需要自己判断了。
4.4 实在不行,重启系统吧,这也是最常用到方法之一。
getpid() fork()的问题
fork()函数用于复制父进程,这个父进程是指当前进程吗?
如果是当前进程,为什么我pid=fork()获得的ID和pid=getpid()获得的ID不一样
pid=fork()与ppid=getppid()获得的ID也不一样
getpid()和getppid()也是分别获得当前的ID和父ID首先你得理解一个fork调用会返回两次,分别在父子进程中返回,并且返回值是不同的。
而fork以下的代码如果不加控制,就会分别在父子进程里面都继续执行下去。
要有效区分“当前”进程是父进程还是子进程的方法就是查看fork的返回值。
假如有一个进程,其pid为100,那么在这个进程来里面调用getpid()应该得到100。
接下来,该进程调用了fork(),产生了pid为101的子进程,那么
在原有的100进程(父)中,fork的返回值是101,也就是子进程的pid;
再次getpid()得到100,getppid()将得到祖父进程的pid,也就是既不是100,也不是101的其它值。
在新生的101进程(子)中,刚才产生了它本身的fork的返回值是0;
再次getpid()得到101,getppid()将得到100。
**的优势:
**提供给客户的不仅仅只是降低员工的需求,服务器综合维护成本降低,安全,全球化,效率提升。
自己思考的优势:还可以帮助游戏开发者,游戏开发团队,游戏开发公司提升知名度,打造游戏开发者的品牌。举例,开心农场的开发者实5分钟公司,开心农场在中国风靡,几乎人人皆知,但是很少有人知道开心农场是5分钟公司开发的。比如在人人网上,很多人玩开心农场,喜欢上开心农场,很自然以后人人网出了其他游戏,很多人会因为喜欢开心农场而且玩人人上的游戏,这就是所谓的爱屋及乌。但是问题出现了,现在假如5分钟公司出了一款新游戏,它没有在人人上发布运营,本来很多人喜欢开心农场,按理会支持5分钟公司的其他产品,但是由于5分钟公司知名度不高,很多人不知道开心农场是5分钟公司开发的,所以如果一旦5分钟公司把自己的产品发布到其他平台,就不一定有很多的受众。所以对于游戏公司来说,要打造自己的核心品牌,要让玩游戏的知道该游戏是自己公司开发的,之后,该公司发布游戏,就不用受制于运营商了。而**可以提供这个平台,不仅给游戏公司提供sns连接平台,更可以通过行云自己的平台来发布他们的游戏,**可以只是作为游戏的发布平台,游戏的所有者是游戏公司,从而帮助提高游戏开发公司的知名度。
转自:http://blogold.chinaunix.net/u1/35320/showart_305024.html
我的系统是ubuntu6.06,最近新装好的mysql在进入mysql工具时,总是有错误提示:
# mysql -uroot -p
Enter password:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
使用网上介绍的方法修改root用户的密码:
# mysqladmin -uroot -p password 'newpassword'
Enter password:
mysqladmin: connect to server at 'localhost' failed
error: 'Access denied for user 'root'@'localhost' (using password: YES)'
现在终于被我找到了解决方法,如下(请先测试方法三,谢谢!):
方法一:
# /etc/init.d/mysql stop
# mysqld_safe --user=mysql --skip-grant-tables --skip-networking &
# mysql -u root mysql
mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';
mysql> FLUSH PRIVILEGES;
mysql> quit
# /etc/init.d/mysql restart
# mysql -uroot -p
Enter password: <输入新设的密码newpassword>
mysql>
方法二:
直接使用/etc/mysql/debian.cnf文件中[client]节提供的用户名和密码:
# mysql -udebian-sys-maint -p
Enter password: <输入[client]节的密码>
mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';
mysql> FLUSH PRIVILEGES;
mysql> quit
# mysql -uroot -p
Enter password: <输入新设的密码newpassword>
mysql>
方法三:
这种方法我没有进行过测试,因为我的root用户默认密码已经被我修改过了,那位有空测试一下,把结果告诉我,谢谢!!
# mysql -uroot -p
Enter password: <输入/etc/mysql/debian.cnf文件中[client]节提供的密码>
至此,困惑多时的问题解决了!
1.linux查找命令
(1)find (2)locate (3)whereis (4)grep
2.开源数据库(hash数据库-键值数据库)了解
(1)redis (2)tokyocabinet
3.mysql安装配置
4.linux shell及sed学习
(1)shell写的mp3自动下载代码学习 (2)sed及sedsed学习
1.handle class
PImpl手法,隐藏实现细节。使用指针代替变量定义体。
2.interface class
使用abstract class,纯虚函数(pure function),多态机制。用父类的接口调用具体子类对象的实现。
3.头文件中只能有声明体,实现部分放到另一文件中。实现与声明分开-接口与实现分离。
[文章] 有关char指针的文章一篇 |
|
|
作者:未知 来源:月光软件站 加入时间:2005-2-28 月光软件站
|
[文章] 有关char指针的文章一篇(转自:http://www.moon-soft.com/doc/9040.htm)
先看以下代码: char *p; p="abc"; 你认为是对的吗?
答案:语法上是对的,但不提倡这种写法。
误区1:没有给p分配内存空间就赋值,怎么会是对的呢? 正解:不少人第一眼将这里的p="abc"看成了*p="abc",然后就做出了以上的论断。这是比较笨笨的错误咯:) 看清楚就好啦,其实赋给p的是"abc"的地址。再说,*p="abc"也不对呀,字符串可不能这么赋值。
误区2:这"abcd"哪来的地址,怎么能直接赋给p呢? 正解:先自己试试吧。在2K/XP + VC下运行这段代码,是不会出错的,说明这段代码并无问题。晕吧?猜想的话呢,就是"abcd"不知道被放在了什么地方,然后弄来了一个地址,给了p。
这到底是怎么回事呢?
要知道,这两个语句和char *p="abc"是完全一样的,所以其中的道理也一样。 char *p="abc"曾经迷惑了不少人呀。问问你:p到底是什么类型的?char *?错,是const char *! 也就是说,它所指向的内容是不可改变的。不过要补充的是,a的指向是可以改变的。 所以为了不再引起误会,char *p="abc这种写法是不提倡的。 既然char *p="abcd"被建议写成const char *p="abcd",那么char *p; p="abcd";也应该写成const char *p; p="abcd";
讲来讲去,最后来得看看汇编代码。看完就明白是怎么回事了。(我才发现汇编代码原来这么爽看!VC下,没开编译器优化): 我们重点先看const char *p="abc"和char p[]="abc"有什么不同(都放在main()中声明):
PHP源码:
void main()
{
const char *p="abc";
}
3: const char *p="abc";
00401028 mov dword ptr [ebp-4],offset string "abc" (0041f01c)
void main()
{
char p[]="abc";
}
3: char p[]="abc";
00401028 mov eax,[string "abc" (0041f01c)]
0040102D mov dword ptr [ebp-4],eax
看出差别了吗?上一段ASM用offset取"abc"的地址,然后赋给[ebp-4],也就是p(下同)。 而下一段ASM却转了一个弯,先把"abc"的地址转到寄存器eax,然后再转赋给p。
有疑问了:为什么不和上面的一样,直接用offset?VC是很聪明的(废话,M$的东西呀),不用offset,恐怕就是用不了了。 offset是一条伪指令,在编译的时候就已经把偏移量算好了。offset是无法执行间接寻址的计算的。
说明了什么?
const char *p="abc"中的"abc",在编译期间就已经处理好,要了一块内存,存起来了!在把地址赋给p的时候,就可以直接用offset计算。 而char p[]="abc"中的"abc",是在运行期间动态分配内存给"abc",然后再算出地址,赋给p。hehe,这同时也说明了数组和指针的等价性。
我们再做一次实验,这一次我们把char p[]="abc"放在main()外,并在main()内用一个指针再指向p看看。
PHP源码:
char p[]="abc";
void main()
{
char *t=p;
}
5: char *t=p;
00401028 mov dword ptr [ebp-4],offset p (00421adc)
这回p[]的声明放在main()外,变成了全局变量。结果main()内的t取p的指针的时候,直接用offset可以计算出来。
据小石头所说,字面值,const,static,inline,全局变量都是放在静态数据区的。(但我感觉似乎不是如此,只是全局变量和字符串在编译时就处理好放在一起)
不难发现,p[]被声明成全局变量后,就可以直接用offset计算地址,说明静态数据区是编译时就已经处理好的。 再对照const char *p="abc"的ASM,我们马上想到:"abc"就是被C/C++存在静态数据区中的!它的地址就是"abc"在静态数据区的地址!
弄清了这个,有些问题也就可以想得通了。下面这种用法,看来不能说是错误的了,因为"abc"是在静态数据区的,生存期可以说是整个程序:
PHP源码:
#include "stdio.h"
const char *fun()
{
const char *p="abc";
return p;
}
void main()
{
const char *t=fun();
printf ("%s",t);
}
看ASM:
PHP源码:
5: const char *p="abc";
00401038 mov dword ptr [ebp-4],offset string "%d" (0042001c)
一样也是使用offset计算地址。
C/C++给字符串的待遇真是太好了。为了一个字符串,几乎可以打破所有的指针规则。晕~~~~(完)
附:第一次写这么长的文章,写得挺晕的。本来我的C/C++也不是很纯熟的,ASM也是一知半解,今天在CSDN上为这个问题郁闷了半天,和几个人讨论了一下,最后就写了这么一篇文章。希望大家赏脸看看~~~有错一定要指正啊!最后特别感谢小石头(想飞的菜鸟,骄傲的石头,菜菜,都是他咯),还有小阿哥(就是Kingzeus咯)的帮忙~~~~~~谢谢咯~~~~~~
__________________ 小菜虎 -> 菜菜的老虎
骄傲的石头回复:
堆几盘积木,心情好些了,所以再重新写一遍。 关于字符串的这个问题,我一直在心里困惑着。所以呢,昨天就看了一下。 以前回答别人的时候,总是很简单的回答,字符串就是const char *指针,指向它的入口地址。现在想来真是惭愧,虽然这个事实好象已经为大家所接受,甚至没有人探讨过这个问题!所以我相信我的发现对大家大多数是有好处的。 首先请看以下代码
PHP源码:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
const char *p = 0;
char *p2 = p;
return 0;
}
以上代码有问题吗? 如果你说没有,请你试一下。很明显,这是有问题的。const是为了保证不变性,而你把他变成non const,肯定有错误或者警告,要么就要用const_cast转换。 所以上面的代码不能通过编译。
那么这就很明显的在lee的post里出现了问题,当然我以前也一直是这么认为,甚至很多人都是这么认为。难道这是编译器对字符串的特殊处理吗? 还是其他的原因?
于是我想看看究竟。就动用了RTTI,我飞快的键入了以下代码。
PHP源码:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout << typeid("abc").name() << endl;
return 0;
}
你说结果是什么? 是char [4]!而不是const char *; 好,这个结果解决了我心中的疑点,原来是这样!这可以很简单的解释char *p = "abc"这个问题。 数组是一个char *const 指针,当然可以赋给char *指针而不会影响其常量性。所以这是完全正确的赋值。 其实这想起来也很平常,指针是没有分配空间的地址而已,而数组是一种容器,占用连续的储存空间。想想字符串就该知道它是一个数组!而不是指针!真正意义上的指针只能是地址,而它在分配了连续的空间后可以作为数组来使用,这是由于他们的共性而决定的。
哈哈!心情愉快,所以也接着看了下lee上面所做的探讨。字符串是放在静态储存区没错,毫无疑问。至于位置~ 偶不想多说,lee在上面分析了很多。所以我只对它进行了一下简单的测试。 我手头上只有dev c++ 和 vc70编译器,所以就只用他们进行了测试。(打开了全部优化)
PHP源码:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
char *p = "abc"
char *p2 = "abc";
p[1] = 'k';
p2[1] = 'j'
cout << p1 << endl;
cout << p2 << endl;
return 0;
}
结果呢?在dev c++下报错, 原因我想可能是因为在dev c++在对静态储存区进行了保护。而vc70下,通过,并且两个输出是不同的。所以偶去看vc70产生的msil码,原来vc70每次处理字符串都在静态区分配了空间,而且这两个"abc"是连续分配的!这里就没有出现Solmyr说的那种情况。我想一般比较好的编译器也应该这么说。至于vc60,偶没有测试,但是可能Solmyr说的情况会出现吧。但这样是不好的,相信这样会出现很多微妙的情况。 所以,引用字符串的最佳格式是const char *指针,但char *指针是完全没错的。于情于理,也说的过去吧 。
如果你还要问,那用指针接受数组呢? 呵呵,你该仔细看看前面了。相信这会给你帮助。
__________________ 不可一日无酒无肉无女人
|