一直不明白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 *指針是完全沒錯的。于情于理,也說的過去吧 。
如果你還要問,那用指針接受數組呢? 呵呵,你該仔細看看前面了。相信這會給你幫助。
__________________ 不可一日無酒無肉無女人
 |