Lập trình

Lập trình

Chủ Nhật, 22 tháng 6, 2014

Bản chất của việc dùng biến trong hàm , shadow copy , ...


Trong C++ hàm rất quan trong nhưng nó sử dụng biến thế nào ta đến với bài hôm nay :

Xét một ví dụ :
 int cong(int a,int b){return (a+b)};

 //trong hàm chính :
 int main(){
    int a = 10;
int b = 5;
cout << "Tong cua a va b la: " << cong(a,b);
return 0;
  }
 
 Trên đây là một ví dụ rất đơn giản về hàm , chúng ta cùng phân tích :
 Khi gọi hàm cong(a,b); thực chất hàm cong làm việc với bản sao của a,b mà không làm việc trực
 tiếp với a,b
 Tức là khi gọi hàm a,b tạo ra một bản sao và đưa cho hàm làm việc (Tạo ra một ô nhớ mới và copy giá trị qua)
 việc copy thế này người ta gọi là shadow copy. Trong C++ nó gọi hàm khởi tạo sao chép để copy giá
 trị mới , tương tự thế này : Ten_class _a(a); (tạo giá trị copy của a là _a), nếu không có hàm sao
 chép khởi tạo thì nó gọi hàm sao chép mặc định.
 Để chứng minh bài này ta làm như sau :

 void ham2(int b){
cout << &b;
 }

 int main(){
int b = 0;
cout << &b << endl;
ham2(b);
return 0;
}

-> kết quả cho 2 giá trị (địa chỉ hoàn toàn khác nhau).
 Trong các bài toán sử dụng đến con trỏ(như swap by pointer) thì thực chất con trỏ đó cũng tạo ra
 một ô nhớ mới để copy ĐỊA CHỈ qua (giống giá trị trong biến) , và đưa cho hàm sử dụng bản sao đó
 Vậy cái thay đổi mà ta nhận được là giá trị của biến có địa chỉ là địa chỉ nằm trong con trỏ,
 chứ không phải con trỏ.

 Xét một số ví dụ :
 void ham(int *p){
     p = new int [5];
for(int i =0 ; i<5 ;i++){
   cout << "Nhap phan tu " << i << " : ";
cin >> *(p+i); // tương đương với p[i], hay i[p];
}
 }

 int main(){
   int *p;
   ham(p);
   delete[] p; // <- Sai
   return 0;
 }

 +Lỗi sai ta gặp phai ở đây là lệnh delete[] p; không gây lỗi cho trình biên dịch , mà gây run-time
 một số trình biên dịch cùi không phát hiện ra. Lỗi sai này là do :
 Như ta đã phân tích ở trên hàm làm việc với bản sao của con trỏ(ta tạm gọi bản sao của p ở trên là _p) ,
 sau đó trong hàm bộ nhớ được cấp cho _p , chứ không phải cho p , nhưng ở hàm main () ta lại
 delete[] p; => sai.

 vd2 :
 void ham1(int *p, int b){
p = &b;
 }

 int main(){
int a = 2;
int *p = &a;

int b = 5;
cout << p << "  " << *p << endl;
ham1(p,b);
cout << p << "  " << *p;
return 0;
}

-> kết quả vẫn cho như cũ vì hàm dùng bản sao của con trỏ chứ có dùng trực tiếp con trỏ đâu mà
con trỏ thay đổi giá trị.

Vậy muốn thay đổi giá trị (địa chỉ bên trong) con trỏ ta làm thế nào ????
Tôi tham khảo ở condongcviet.com .

Cách 1 : dùng tham chiếu trong C++

void ham(int *&a)
{
    a=new int[100];
}
void ham(int **&a)
{
    a=new int*[100];
}
xin chú ý là * đứng trước &

Cách 2 : up level của * dùng con trỏ cấp cao hơn con trỏ hiện tại

#include <stdio.h>
#include <conio.h>

void ham(int **a)
{
    *a=(int*)malloc(100*sizeof(int));
    //a[0]=(int*)malloc(100*sizeof(int));
    // 2 cach nay nhu nay
}

void main()
{
    int *a;
    ham(&a);

    free(a);
}



Trước khi kết thúc bài này ta đến với một điều rất thú vị (học đến class)

ta đã biết trong class có hàm tạo sao chép dùng để copy cho đối tượng mới một số thuộc tính
cụ thể nào đó của đối tượng đã xác định từ trước:

class Tenclass{
  private :
   thuộc tính ....
  public :
    //hàm tạo sao chép :
Tenclass(const Tenclass & dt1){....}; // <- chú ý chỗ này nhé
}
//hàm main()
int main(){
   Tenclass dt;
   //làm việc với thuộc tính của đối tượng dt.
   .....................
   // copy đối tượng mới :
   Tenclass newdt(dt1); //<- chú ý chỗ này nữa
   return 0;
}
Tại sao hàm copy constructor không sử dụng tham biến luôn đi lại còn sử dụng tham chiếu :
Tenclass(const Tenclass dt1){....};// cách viết 2
qua bài học này ta có thể giải thích như sau:
Trong hàm main() ta có lệnh gọi : Tenclass newdt(dt1);
lệnh này gọi hàm tạo sao chép nhưng hàm tạo sao chép ày dùng bản sao của dt1 , vì thế dt1 phải
sao chép ra một đôi tượng mới là _dt1 để cho hàm tạo sao chép sử dụng , không may trong quá trình
tạo đối tượng mới _dt1 , để tạo nó thì lại phải gọi hàm tạo sao chép và có tham số đầu vào vẫn là
dt1 , ..... => tiếp tục như quá trình trên gây run-time.

Thứ Bảy, 21 tháng 6, 2014

Sai lầm trong suy nghĩ về hàm và con trỏ

Copy ở : condongcviet.com , 

Xem đầy đủ tại đây  Mình post thế này cho tiện :)



Có nhiều thật nhiều người nói rằng trong C, ta có thể sử dụng con trỏ trong tham số của hàm như là 1 tham biến, qua hàm ta có thể thay đổi được giá trị của tham số.
tôi xin khẳng định lại, điều này thật là 1 hiểu lầm, sai lầm trong suy nghĩ, 1 sự hiểu biết nông cạn, 1 câu phát biểu kiểu ù ù cạc cạc!!!

Nguyên nhân
+ Hàm trong C ko hề có tham biến, hàm trong C đều hoạt động theo nguyên tắc sau :
Khi gọi hàm, 1 bản sao của tham số được tạo ra (cấp phát vùng nhớ mới, copy giá trị sang. quá trình này theo giáo trình của đại học FPT gọi là shadow copy, là 1 yếu tố cần quan tầm, 1 C/C++ Developer đừng bao giờ quên điều này), và hàm sẽ làm việc với bản sao này
(trong C++ nó sẽ dùng hàm tạo sao chép để tiến hành quá trình shadow copy này)

+ Vậy khi làm việc với con trỏ thì hàm làm thế nào
vâng, hàm vẫn cứ làm theo nguyên tắc 1 và 1 bản sao của con trỏ được tạo ra, và hàm làm việc với bản sao hàm, và trước khi gọi hàm con trỏ trỏ vào đâu thì nó vẫn được trỏ vào đấy chứng minh :
C++ Code:
  1. #include <stdio.h>
  2. #include <conio.h>
  3.  
  4. int ham(int *a)
  5. {
  6.     *a=2;
  7.     a++;
  8. }
  9. void main()
  10. {
  11.     int *a;
  12.     printf("Truoc : %x",a); //trước và sau khi gọi hàm
  13.     ham(a);                    //con trỏ a trỏ vào đâu
  14.     printf("Sau %x",a);     // thì nó vẫn trỏ vào đó
  15.     getch();
  16. }


+ Vậy tại sao lại có sự thay đổi và tại sao lại sử dụng con trỏ trong hàm? Con trỏ ko thay đổi thì cái gì thay đổi được ?
vâng, các bạn chú ý nhé, giá trị nằm trong vùng nhớ trỏ đến thay đổi. Vâng đúng thế đấy bạn à, do biến của ta nằm trong vùng nhớ được trỏ đến nên nó được thay đổi
C++ Code:
  1. #include <stdio.h>
  2. #include <conio.h>
  3.  
  4. int ham(int *a)
  5. {
  6.     *a=2; // làm việc với địa chỉ nhận được
  7. }
  8. void main()
  9. {
  10.     int n;
  11.     ham(&n);// truyền địa chỉ của n vào đây
  12.     // do đó sau hàm này n =2
  13.     getch();
  14. }

Thứ Sáu, 20 tháng 6, 2014

Con trỏ hằng và hằng con trỏ

Con trỏ hằng và hằng con trỏ là hai khái niệm rất khác nhau và dễ nhầm lẫn hôm nay ta đến với khái niệm và cách dùng của 2 kiểu con trỏ trên :

Định nghĩa :
Ở đây theo việt hóa mà nói thì :
- Con trỏ hằng là con trỏ trỏ đến một vùng nhớ cố định , thông qua con trỏ ta không thể thay đổi giá trị của vùng nhớ đó , nhưng con trỏ có thể trỏ đến vùng nhớ khác . VD : char *p = "Chao cac ban";
  Ở trên p trỏ đến vùng nhớ "Chao cac ban"  không thể làm thay đối giá trị của vùng nhớ đó , nhưng rõ ràng p có thể trỏ đến vùng nhớ khác .
-Hằng con trỏ là con trỏ trỏ đến cố định một vùng nhớ , tức là ta không thể khiến con trỏ trỏ đến vị trí khác , nhưng qua con trỏ ta có thể làm thay đổi giá trị của vùng nhớ đó . VD : int pt[100];
Ở đây pt trỏ đến cố định một vùng nhớ (chứa 100 phần tử của mảng) , nhưng qua pt ta có thể làm thay đổi giá trị của vùng nhớ : pt[5] = 9;*(pt+4) = 10; không hề sai.

Cách khai báo :
Trước tiên ta đến với cách khai báo hằng trong biến bình thường , ta có 2 cách để khai báo hằng của biến bình thường :
const kieu_gia_tri ten_bien;
hoặc kieu_gia_tri const ten_bien;

Ta thấy không sự khác biệt trong tác dụng của định nghĩa hằng của biến thông thường kiểu này , nhưng khi chuyển qua con trỏ ta cần phân biệt 2 cách sử dụng này !

Xét cách khai báo 1:
kieu_du_lieu *const ten_con_tro; 
ta thấy ở biến bình thường kiểu dữ liệu được viết là kieu_du_kieu , sang bên con trỏ được viết kieu_du_lieu * ,thực chất kiểu dữ liệu của con trỏ là int * , char * ,....

đây là cách khai báo một hằng con trỏ vd :
   
        int g,h;
int * const pt = &g; // khai bao mot hang con tro
cout << pt;
*pt = 9;
pt = &h; // Sai vi pt la hang con tro
Khi nói một thứ là hằng thì những thuộc tính mà nó đại diện không đổi trong quá trình làm việc , ví dụ biến thì có thuộc tính lưu trữ giá trị khi coi nó là hằng thì giá trị của biến không đổi , qua con trỏ , con trỏ có thuộc tính lưu trữ địa chỉ thì khi con trỏ coi là con trỏ hằng thì địa chỉ mà nó lưu trữ luôn không đổi.

-Xét cách khai báo 2 :
const kieu_gia_tri* ten_con_tro ; là cách khai báo một con trỏ hằng.

Một vài chú ý của xâu kí tự - char []...

Trong lập trình c++ , ta sử dụng tương đối nhiều đến xâu kí tự char , thực chất xâu kí tự là một mảng một chiều kiểu char , nhưng trong quá trình sử dụng chúng ta vẫn còn một số sai sót . Vì thế hôm nay tut mình đi sâu vào những sai sót đó mong là hữu ích cho các bạn chưa biết .
Mình kết hợp với một vài nguồn tài liệu...

Những sai sót khi sử dụng mảng kí tự :
1.Chưa cấp phát bộ nhớ mà đã sử dụng xâu :
 vd ta khai báo một con trỏ :
   char *p; cin.getline(p,60);
Ở đây ta chưa cấp phát bộ nhớ cho p mà đã sử dụng nó  . Khi debug vẫn chạy được nhưng xuất hiện lỗi run-time , tóm lại nó thuộc lỗi cấp phát .
2. Thay đổi giá trị của con trỏ hằng .
Con trỏ hằng là con trỏ trỏ đến vùng nhớ cố định , tức là thông qua con trỏ đó ta không thể thay đối giá trị của vùng nhớ đó , nhưng ta vẫn có thể khiến con trỏ đó trỏ đi vị trí khác
VD : ta khai báo :
     char *ch = "Chao cac ban"; // hoặc char ch[] = "Chao cac ban".
    ch[2] = 'c'; //khi ta viết ch[2] thì trình biên dịch hiểu là *(ch+2), 2 cách viết hoàn toàn giống nhau
Lúc này ch như một con trỏ, trỏ đến hằng xâu là "Chao cac ban" , nhưng ta cố tình thay đổi giá trị của vùng nhớ thông qua con trỏ bằng câu lệnh *(ch+2) = 'c' .

3. Khi học mảng kí tự ta có một chú ý rất quan trọng như sau :
Đại loại là : ta chỉ có thể khởi tạo giá trị cho mảng kí tự khi khai báo mảng kí tự đó :
vd : char ch[] = "Chao cac ban" ; // Đúng
 nhưng char ch[60]; ch  = "Chao cac ban" ; //Sai 
Bản chất của việc này như sau :
Khi ta khai báo char ch[60]; thì bộ nhớ lấy 60 phần tử liên tiếp nhau cấp phát cho con trỏ ch để nó quản lý , ch là một hằng con trỏ tức là nó luôn trỏ vào vị trí đã được cấp phát đó , ngoài ra không thể trỏ đi dâu được nữa . Nhưng lệnh ch = "Chao cac ban"; đã ép nó trỏ đến địa chỉ khác , cụ thể là địa chỉ của hằng xâu "Chao cac ban ", nằm ở đâu đó trong vùng nhớ.
Để sửa lỗi này ta làm như sau :
char ch[60] = "Chao cac ban";

4.Còn một lỗi sai nghiêm trọng khác đó là :
 So sánh nội dung 2 con trỏ :

Trước tiên ta đến với bản chất của việc so sánh 2 con trỏ , việc so sánh ngang bằng là việc so sanh xem 2 con trỏ có trỏ cùng vào một vị trí hay không , xem con trỏ có trỏ vào NULL hay không

Còn phép so sánh > , < là so sánh sự cao thấp của 2 địa chỉ , con trỏ lớn hơn thì địa chỉ cao hơn , con trỏ nhỏ hơn thì có địa chỉ thấp hơn .

Xét ví dụ :
 char ch[] = "Chao ban";
if (ch=="Chao ban"){........};
Việc so sánh không sai về ngữ pháp nhưng nó mạng lại kết quả không mong muốn , thực chất việc so sánh trên là xem ch , hằng xâu "Chao ban" có trỏ vào cùng một địa chỉ hay không hay ô nhớ của 2 thằng trên có cùng một giá trị (giá trị này là địa chỉ) hay không . Thực tế điều này được lưu ý khi học mảng kí tự chúng ta có hàm strcmp để so sánh 2 xâu.

Thứ Hai, 16 tháng 6, 2014

Một số trường hợp của con trỏ c++

Nguồn tham khảo : condongcviet.com , và một số nguồn khác ...

Trước khi vào bài bài viết ta cùng tìm hiểu con trỏ void :
theo wiki : Ngôn ngữ C và C++ cũng cung cấp con trỏ chỉ kiểu void (được đặc tả bởi void *), nhưng đây là một khái niệm không có liên quan. Các biến kiểu này là các con trỏ chỉ đến dữ liệu của một kiểu chưa được xác định, vì thế ở ngữ cảnh này (nhưng không phải trong những ngữ cảnh khác) void đóng vai trò như một kiểu vạn năng hay kiểu top. Một chương trình có thể chuyển đổi một con trỏ chỉ đến kiểu bất kì của dữ liệu thành một con trỏ chỉ đến kiểu void và quay ngược lại kiểu ban đầu mà không làm mất thông tin, điều này khiến cho các con trỏ trở nên hữu ích trong các hàm đa hình (polymorphic function) (chú ý điều này không thật đúng đối với các con trỏ hàm vì các hàm không phải là dữ liệu[1]).

Bài viết khác có liên quan : 
Con trỏ void là loại con trỏ đặc biệt. Trong C++, void dùng để quy định sự không tồn tại của một kiểu dữ liệu (hay kiểu dữ liệu rỗng). Vì vậy, con trỏ void là con trỏ trỏ vào giá trị có kiểu dữ liệu void (cũng vì lẽ đó, mà nó không xác định độ dài và thuộc tính tham chiếu ngược).
Con trỏ void cho phép trỏ sang một kiểu dữ liệu bất kì. Nhưng khi chuyển đổi, chúng có một giới hạn rất lớn: dữ liệu trỏ bởi chúng không thể trực tiếp tham chiếu ngược, và vì nguyên nhân này, chúng ta cần ép kiểu địa chỉ của con trỏ void sang một con trỏ khác mà nó có một kiểu dữ liệu cụ thể trước khi tham chiếu ngược trở lại.

Chương trìnhKết quả
#include using namespace std;
void increase(void* data, int psize)
{
if(psize==sizeof(char))
{
char* pchar;
pchar=(char*)data;
++(*pchar);
}else if(psize==sizeof(int))
{
int* pint;
pint = (int*)data;
++(*pint);
}
}
int main()
{
char a = ‘x’;
int b = 1602;
increase(&a, sizeof(a));
increase(&b, sizeof(b));
cout<<<”, “<<
return 0;
}
y, 1603
Giải thích: hàm increase có hai tham số: tham số data là một con trỏ void, tham số psize là kích thước của con trỏ data. Câu lệnh if sẽ kiểm tra điều kiện xem biến trỏ data thuộc kiểu dữ liệu nào – nếu psize==sizeof(char) thì nó là con trỏ kiểu char, tương tự nếu psize==sizeof(int) thì nó là con trỏ kiểu int. Vì ở đây, ta chưa xác định được nó là con trỏ kiểu gì, nên ta sẽ sử dụng tham số là con trỏ void. Nếu là con trỏ char, ta sẽ sử dụng biến trỏ pchar để khởi tạo giá trị cho nó bằng cách ép kiểu từ con trỏ void. Hoàn toàn tương tự cho biến trỏ pint. Mục đích của hàm increase là tìm giá trị tiếp theo của tham số data. Trong hàm main, ta sử dụng hai biến char và int. Giá trị tiếp theo của kí tự ‘x’ là kí tự ‘y’, của số 1602 là 1603.

Một số trường hợp đặc biệt trong con trỏ (copy từ congdongcviet rất hữu ích)

1. Hiểu lầm về cách cho p trỏ vào a






2. Cùng trỏ vào 1 biến




3. Con trỏ đa cấp




4. Con trỏ trỏ đến ô nhớ đã biết




5. Con trỏ void
Con trỏ void là 1 con trỏ đặc biệt, thích trỏ đi đâu thì trỏ

PHP Code:
int ham() 

    return 
1

void main() 

    
int a
    
void *p,*q
    
p=ham
    
q=&a
}  




Chủ Nhật, 15 tháng 6, 2014

Vấn đề về cấp phát động cho con trỏ của private

Khi định nghĩa class như sau :
 class SinhVien{private : int tuoi = 18 // Điều này là sai , trong class của c++ không cho phép khởi tạo giá trị cho biến mà chỉ cho khởi tạo khi ở trong constructor

}

Đây là điều lý giải tại sao :
class SinhVien{
private : char * x; chú ý chỗ này
public : 
SinhVien(){x = new char [10]; } //chú ý chỗ này nữa
};

thì cái này chạy bình thường , nhưng khi mình cấp phát luôn cho thuộc tính char*x = new char [10]; thì nó lại đưa ra cảnh báo :[Warning] non-static data member initializers only available with -std=c++11 or -std=gnu++11 [enabled by default]


Nguồn tham khảo : congdongcviet.com

Thứ Bảy, 14 tháng 6, 2014

Bản chất của việc cấp phát bộ nhớ động và giải phóng bộ nhớ

Nguồn : http://diendan.congdongcviet.com/threads/t36221::ban-chat-cua-viec-cap-phat-bo-nho-dong-giai-phong-bo-nho.cpp        bài viết khá hay nên minh copy qua đây




1. Dẫn nhập
Bộ nhớ là 1 tài nguyên quan trọng trong hệ thống , được phân phối điều phối phức tạp.

ở đây, chúng ta sẽ xét tương quan như sau cho dễ hiểu : đất là 1 tài nguyên quý giá trong thành phố và được điều phối bởi sở tài nguyên môi trường
(coi như thế, giờ hình như nó thuộc sở nhà đất thì phải,nói tóm lại là 
sở quản lí)


2. Cấp phát bộ nhớ :
Bộ nhớ quan trọng như thế ko thể tùy tiện sử dụng , vì trong máy có nhiều tiếng trình cần bộ nhớ, tùy tiện sử dụng thì lấy đâu ra mà dùng ????

ai cũng cần có đất, cứ tùy tiện lấy gì cần gì sở quản lý, như thế sẽ có người không có đất mà dùng ???



Cấp phát bộ nhớ hoạt động như thế nào : bản chất là trao quyền sử dụng.

hôm nay 1 người (1 con trỏ) xin cấp phát (malloc, new) 1 vùng đất, anh ta được trao quyền sử dụng 1 vùng đất đó. 
Khi này :
Có thể trên mảnh đất này đang trống rỗng (toàn null) nhưng cũng có thể trên đó đầy cỏ rác,hoa màu hoặc nhà cửa do người trước đó sử dụng vẫn còn . (điều này đối với anh ta mà nói nó hoàn toàn là ngẫu nhiên, ko ai có thể khẳng định được cả)
>>>>> khi ta xin cấp phát xong thì vùng nhớ đó hoàn toàn có giá trị ngẫu nhiên


Nếu như khi anh ta xin cấp phát mà có đút lót để ra thêm điều kiện hoặc trước đó anh ta có thuê thêm 1 người quét dọn xây sửa thì anh sẽ có được 1 mảnh đất có sẵn cái anh ta muốn
>>>> khi ta cấp phát đi cùng với ép buộc sẽ được vùng nhớ có giá trị như ta mong muốn
ví dụ 
PHP Code:
int *a=new int(4);int *b=(int*)malloc(1000);memset(b.....);
static 
int x[1000];
static 
int y[10]={1,2,3,4,5,6};  
<<<<<<<< toàn bộ điều này là đáp án tại sao khi cấp phát bộ nhớ lại có giá trị ngẫu nhiên



3. Giải phóng bộ nhớ như thế nào

Anh ta dùng xong đất rồi, ko cần nữa, đem trả quyền sử dụng mảnh đất lại cho khổ chủ.

Giải bộ nhớ hoạt động như thế nào : bản chất là trả lại quyền sử dụng
khi này :
sở sẽ thu lại quyền sử dụng đất của anh, thế là đủ rồi, sở ko cần phải động chạm sửa chữa lại gì vùng đất đó. Nên khi đó dù vùng đất đó đã được giải phóng nhưng mà hoa màu của a ta trên đó thì vẫn còn, ko có biến mất được

điều này giải thích tại sao khi mà trong 1 số hệ điều hành đời cổ, (các bạn lính mới bây giờ chắc không gặp) nhiều bạn thử giải phóng bộ nhớ rồi, delete rồi, free rồi mà khi in thử ra vẫn thấy kết quả như thế .
đơn giản thôi, a ta trả lại mảnh đất đó, nhưng chưa có ai thuê nó cả, chưa có ai sử dụng nên hôm sau ra xem thử thì nó vẫn như hôm trước khi a ta trả lại 
Các bạn đọc rõ chú ý ở dưới rồi hẵng hỏi thêm về câu này nhé.

Và các bạn phải chú ý là quyền sử dụng đất mới là cái được quan tâm duy nhất của vấn đề này

PHP Code:
int *a=new int[10];int *b=(int*)malloc(1000);  
thì giá trị trong a và b là địa chỉ của vùng nhớ được cấp phát. địa chỉ này sẽ được lưu lại trong sởsở sẽ dùng cái số địa chỉ này để hiểu là quyền sử dụng đất khi giải phóng

tiếp theo
PHP Code:
free(b);delete []a;  
2 câu lệnh này ko có làm thay đổi giá trị của a và ba và b vẫn trỏ vào mảnh đất đấy. ok ? Và càng ko can thiệp vào vùng nhớ mà a ,b trỏ đến, cái duy nhất mà nó làm là xác nhận vùng nhớ đó đã được trả lại, đang rảnh rỗi, có thể cho thuê tiếp.

<<< điều này giải thích cho cách hiểu sai lầm của 1 số bạn mới học đối với việc cấp phát và giải phóng



4. Memory leak

Câu hỏi mở rộng :
như thế này có phải là giải phóng ko
PHP Code:
    int*x=new int[20];
    
x=NULL;  <<<<< như thế này có phải là giải phóng ko  
ở trong C (not java, java khác) thì :
câu trả lời là không , bạn đã hiểu sai lầm hoàn toàn về việc giải phóng rồi, bạn nên đọc kĩ lại phần giải phóng để hiểu tại sao. 
như thế này chính là :
anh ta đánh mất cái quyền sử dụng đất của mình, kết quả là vùng đất đó ko được sở lấy về, sau nhiều lần như thế thì tài nguyên đất của sở càng ngày càng ít đi, hiện tượng này gọi mà memory leak


Sau khi chương trình kết thúc , việc bộ nhớ xin cấp phát có được tự giải phóng hay ko thì tùy thuộc vào cài đặt của trình biên dịch và hệ điều hành. Chúng ta có 2 khả năng 
· Khả năng 1 : trong phần khởi động (trước khi vào function main() ), program xin một khối memory cuả system gọi là XXXX và mỗi lần malloc được gọi thì cấp pháp một phần memory cuả XXXX cho malloc. Do đó, khi program exit thì trả XXXX về cho system là xong. Trong trường hợp này không bị mất memory
· Khả năng 2 : mỗi lần malloc được gọi thì xin memory từ system . Trường hợp này có thể memory không trả về cho system. Lúc này chương trình sẽ bị gọi là có memory leak.Như vậy sau vài lần chạy sẽ ko còn memory để cấp phát nữa


Đó là giải thích tại sao có nhiều chương trình chạy xong, thoát ra rồi, f5 mấy lần rồi mà máy vẫn cứ ì ì ì ra..........


5. Trên đây là cách lý thuyết cơ bản dành cho các bạn mới học, nó được áp dụng đối với lập trình trên console, (cấp sở tài nguyên thôi)
trong windows nó cũng như thế thôi nhưng mà nó có thêm nhiều quy tắc rằng buộc chặt chẽ hơn rất nhiều (cấp bộ tài nguyên và môi trường, phải khác chứ)

Muốn biết thêm về bộ nhớ thì xem chap 1 của tut dưới đây : 
 Portable Executable File Format




Update 9/9/10
Câu hỏi
Nhân tiện cho em hỏi, nếu mình khai báo 1 mảng có thể có rất nhiều phần tử thì bộ nhớ của nó sẽ được lấy từ đâu ra (Ổ cứng, Ram.... Hay là một cái gì đó mà em không biết :P). Khi em khai báo 1 mảng int a[1000000] thì nó báo là "Array size too large". Mới có 1000000 phần tử nó đã coi là lớn thì những bài toán thực tế vài triệu phần tử liền thì phải làm thế nào? hix
Trả lời
int a[1000000] ; khai báo ở đâu ?
em khai báo ở sở (tức là khai báo dạng local, trong các hàm )thì sở nó xin vùng nhớ ở stack segment, mà stack segment thì đây (trong ảnh, nhỏ gọn thế này, liệu nó có cho em vùng lớn thế kia ko 1000000*4byte)
 Click vào ảnh để xem hình đẩy đủ


đối với các bài toán cần nhiều vùng nhớ 1 cách kinh khủng thì ta thường dùng các hàm api về malloc để lấy các vùng nhớ ở free store được bộ (os-hệ điều hành) cấp phát, 

và 1 điều ko kém phần quan trọng đó là ta nên xây dựng những giải thuật thật thông minh chứ ko phải là load tràn hết lên bộ nhớ
ví dụ xây dựng trước các phương pháp xử lý các sự kiện có thể xảy ra 
giả sử như này, tôi có 1 khối thông tin, để bắt sự kiện "tìm nhân viên mới vào công ti", tôi lập ra 1 temponary, mỗi khi chèn thằng mới vào khối thông tin của tôi, tôi gán thằng temponary này bằng với nhân viên mới, như thế tôi đã có 1 phương pháp sử lí sự kiện tốt rồi đúng ko ???


Update 10/9/10
câu hỏi

Cấp phát động thì nó lấy bộ nhớ từ đâu vậy cà.
"data segment"
Là cái gì thế em dịch là dữ liệu phân đoạn, nghĩa là sao đây, anh langman bận rộn thế :P:P

a. theo giáo trình đại học FPT về OS,theo giáo trình thầy phạm văn ất về tổ thức bộ nhớ, khi tiến trình chạy , bộ nhớ được mô hình thành 4 phần như sau 


(trên thực tế, trong windows, môi trường ảo hóa cụ thể này thì có cách tổ chức phức tạp hơn phát triển từ lý thuyết này)
b. đang bận ôn thi ko có time online được


Update 20/11/10
Câu hỏi của quyết 1991 : sự khác nhau giữa malloc và new?


new và malloc khác nhau cực cực kì nhiều đó các pạn à
sơ bộ như sau, chưa phân tích kĩ
malloc là hàm, cấp phát trả về kiểu void *, malloc thì ko gọi hàm tạo
free ko gọi hàm hủy
malloc trả về NULL nếu thất bại

new là toán tử, new gọi hàm tạo, new có thể được đa năng hóa (nạp chồng), 
new ném ra exception nếu thất bại
toán tử new và toán tử new[] ko có khả năng realloc



Update tiếp câu hỏi hay 2/3/2010
Trích dẫn Nguyên bản được gửi bởi first_pace Xem bài viết
Anh cho em hỏi rõ hơn về đoạn này. Khi giải phóng rồi mà a và b vẫn trỏ đến mảnh đất đó thì nghĩa là nó vẫn có quyền kiểm soát khu vực đó, như vậy thì sao biết là khi đó đất là available đựoc ạ. Em có test một đoạn chương trình như sau.
C Code:
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main(){
  5.     int *p;
  6.    
  7.     p= new int[2]; // xin chính quyền cấp hai khu đất
  8.     p[0]=8888; // khu này trồng rau
  9.     p[1]=9999; // khu này nuôi lợn
  10.     cout << p[0] << "  " << p[1] << endl; // xem tình hình ruộng rau và chuồng lợn
  11.    
  12.     delete [] p; // trả lại đất cho chính quyền
  13.     cout << p[0] << "  " << p[1] << endl; // xem chính quyền làm gì với ruộng rau và chuồng lợn
  14.    
  15.     system("pause");
  16.     return 0;
  17. }
kết quả là em không thấy ruộng rau và chuồng lợn đâu nữa  


em nghĩ có hai nguyên nhân
  • Thứ nhất: chính quyền đã làm gì đó với ruộng rau và chuông lợn của em (sửa đổi nội dung của vùng nhớ mà trước đó p trỏ tới, thay đổi 8888 và 9999) nhưng như anh nói nó không can thiệp vào.
  • Thứ hai: em nhìn nhầm sang một khu đất khác chứ không phải khu đất lúc trước nữa. Nhưng anh nói sau khi bị tứoc đoạt quyền sử dụng đất thì em vẫn nhìn vào khu đất đó (p vẫn trỏ tới vùng nhớ mà trước đó nó trỏ tới)

Nếu thế thì em không giải thích được kết quả của chương trình trên. Anh giải thích kỹ hơn giùm em với. Cám ơn anh 

xin khẳng định lại 1 lần nữa, e đã hiểu hoàn toàn sai lầm, FREE và DEL ko bao giờ thay đổi giá trị của con trỏ, hãy phân việt rõ ràng giữa giá trị của con trỏ và giá trị nó trỏ đến

free nó là void free(void *mem) vậy thì xin hỏi con trỏ mem làm sao có thể thay đổi giá trị qua hàm này (xin hãy đọc bài bản chất con trỏ trong C để hiểu sâu hơn vấn đề này, tip : phân biệt rõ ràng giữa giá trị của mem và giá trị của thằng mà mem trỏ tới)


check lại đoạn code sau đây

C++ Code:
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main(){
  5.     int *p;
  6.  
  7.     p= new int[2]; // xin chính quyền cấp hai khu đất
  8.     p[0]=8888; // khu này trồng rau
  9.     p[1]=9999; // khu này nuôi lợn
  10.     cout << p[0] << "  " << p[1] << endl; // giá trị trỏ đến
  11.     printf("%x\n",p);     // giá trị của p <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  12.    
  13.  
  14.     delete [] p; // trả lại đất cho chính quyền
  15.     cout << p[0] << "  " << p[1] << endl; // xem chính quyền làm gì với ruộng rau và chuồng lợn
  16.     printf("%x\n",p); // giá trị của p  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  17.  
  18.     system("pause");
  19.     return 0;
  20. }



điều nữa
Giải bộ nhớ hoạt động như thế nào : bản chất là trả lại quyền sử dụng
khi này :
sở sẽ thu lại quyền sử dụng đất của anh, thế là đủ rồi, sở ko cần phải động chạm sửa chữa lại gì vùng đất đó. Nên khi đó dù vùng đất đó đã được giải phóng nhưng mà hoa màu của a ta trên đó thì vẫn còn, ko có biến mất được

điều này giải thích tại sao khi mà nhiều bạn thử giải phóng bộ nhớ rồi, delete rồi, free rồi mà khi in thử ra vẫn thấy kết quả như thế .
đơn giản thôi, a ta trả lại mảnh đất đó, nhưng chưa có ai thuê nó cả, chưa có ai sử dụng nên hôm sau ra xem thử thì nó vẫn như hôm trước khi a ta trả lại
giả sử ngay khi em vừa trả , có thằng khác nó đến thuê luôn thì lập tức nó bỏ hoa màu của em đi, trồng hoa màu khác thì em quay lại làm gì còn thời đại này nhanh như ăn cắp ý mà

(windows có bao nhiêu process, thread,? nên việc bị thuê ngay lập tức là quá bình thường, em à, đặc biệt nếu là hàng nằm trên stack thì thay đổi liên tục là bt)


More :
Trích dẫn Nguyên bản được gửi bởi first_pace Xem bài viết
Thanks anh, giá trị của con trỏ và giá trị của vùng nhớ mà con trỏ trỏ tới thì em biết. Em chỉ thắc mắc không hiểu tại sao khi kiểm tra lại thì giá trị của vùng nhớ lại bị thay đổi (8888 và 9999) nên em với đặt ra hai khả năng:
  • Thứ nhất: chính quyền đã làm gì đó với ruộng rau và chuông lợn của em (sửa đổi nội dung của vùng nhớ mà trước đó p trỏ tới, thay đổi 8888 và 9999) nhưng như anh nói nó không can thiệp vào.
  • Thứ hai: em nhìn nhầm sang một khu đất khác chứ không phải khu đất lúc trước nữa. Nhưng anh nói sau khi bị tứoc đoạt quyền sử dụng đất thì em vẫn nhìn vào khu đất đó (p vẫn trỏ tới vùng nhớ mà trước đó nó trỏ tới)

Và theo câu trả lời của anh nó là khả năng thứ nhất, bị thằng nào đó thuê lại ngay sau đó:

Thanks anh đã giải đáp đoạn này, em không biết là nó lại bị thuê luôn như thế, em hiểu rồi 
khi em cài hook bắt các API trên window sẽ thấy LocalAlloc, RemoteAlloc,GlobalAlloc... được xài rất nhiều lần trên /1s chứ ko phải (ví dụ nhỏ xem ở hình dưới , đó là chỉ trong 1 tiến trình firefox thôi đó, chứ ko phải xét trên 5x process đang chạy)


tuy nhiên đối với các môi trường cũ, đặc biệt như trên borland C for dos thì hiện tượng free rồi mà còn tài nguyên rất hay xảy ra, có rât rất nhiều người hiểu lầm là lỗi, rồi ... (đã có rất nhiều câu hỏi như thế trong box thắc mắc C). đó là lý do vì sau a phải nhấn mạnh điều đó