Thursday, November 3, 2016

Stack và Heap

1. Stack
- Cấp phát(allocate) vùng nhớ cho biến nhanh hơn trên heap.
- Các biến được khởi tạo trên stack sẽ tự hủy(deallocate) khi ra khỏi scope.
- Hoạt động theo nguyên tắc FILO(First In Last Out).
- Lưu trữ các biến local, địa chỉ trả về của hàm(return address) lúc hàm được thực thi, các tham số truyền vào hàm(parameter).
- Sử dụng quá nhiều sẽ bị tràn stack(stack overflow), thường do đệ quy, vòng lặp vô tận, hoặc dữ liệu có kích thước lớn.
- Dữ liệu trên stack có thể sử dụng trực tiếp không thông qua con trỏ(pointer).
- Thường được cấp phát kích thước tối đa khi chương trình bắt đầu chạy.
- Sử dụng stack phải biết chính xác kích thước vùng nhớ cần cấp phát lúc runtime.
- Trong các chương trình multi-thread, mỗi child-thread sinh ra sẽ có stack riêng.

2. Heap
- Cấp phát(allocate) vùng nhớ chậm hơn stack.
- Chứa dữ liệu được cấp phát bằng cách gọi hàm new, allocate. Không tự hủy(deallocate) khi ra khỏi scope, phải hủy bằng hàm delete hoặc free.
- Có thể không cấp phát được nếu chương trình yêu cầu cấp phát vùng nhớ quá lớn và heap không đủ vùng nhớ để cấp phát.
- Vùng nhớ heap bị phân mảnh(fragmentation) khi cấp phát và hủy nhiều lần.
- Có thể truy cập dữ liệu thông qua con trỏ(pointer).
- Có thể bị leak memory.
- Sử dụng heap có thể cấp phát động kích thước lúc run-time mà không cần biết trước.

Sunday, April 24, 2016

New, Malloc và Calloc để tạo mảng động

* new, malloc có thể được dùng với những công dụng khác, trong bài này chỉ nói đến việc sử dụng để tạo mảng động.

1. Malloc

void* malloc(size_t size);
malloc là phương thức để xin cấp phát một vùng nhớ trên heap. Hàm malloc nhận vào kích thước của vùng nhớ cần cấp phát(tính theo byte) và trả về con trỏ void.
Vì thế để tạo mảng động với malloc ta phải tính ra kích thước vùng nhớ của mảng cần tạo. Sử dụng sizeof để lấy kích thước kiểu dữ liệu của phần tử trong mảng rồi nhân với độ dài mảng.

int* arr = (int*)malloc(sizeof(int) * 10);
Trong đoạn code trên, ta tạo mảng có độ dài 10 phần tử. Hàm malloc trả về con trỏ void nên ta phải ép kiểu(cast) về kiểu dữ liệu của phần tử trong mảng. Trường hợp không còn bộ nhớ hợp lệ để cập phát, hàm malloc sẽ trả về 0. Trường hợp tạo thành công hàm trả về con trỏ trỏ đến vùng nhớ mới được cấp phát, và trong vung nhớ này chứa toàn giá trị rác(junk memory).
2. Calloc

void* calloc(size_t num, size_t size);
Hàm calloc để xin cấp phát một vùng nhớ và reset vùng nhớ bằng giá trị 0. Tham số num là số phần tử của mảng, size là kích thước kiểu dữ liệu của mỗi phần tử.
Cũng như hàm malloc, hàm calloc cũng xin cấp phát một vùng nhớ trên heap. Nhưng trong hầu hết các trường hợp, sau khi tao xong vùng nhớ ta đều gán một giá trị khởi tạo cho vùng nhớ đó thường là giá trị 0, để tránh vùng nhớ chứa giá trị rác. Hàm calloc tương đương với khởi tạo vùng nhớ malloc rồi reset vùng nhớ bằng memset
3. New
Operator new cũng được dùng để khởi tạo mảng động. Khác với malloc, toán tử new tự động tính kích thước kiểu dữ liệu của phần tử, và trả về con trỏ đã được ép kiểu. Hàm malloc sau khi cấp phát vùng nhớ xong, vùng nhớ vẫn chứa giá trị rác, nhưng với toán tử new, sau khi cấp phát mỗi phần tử sẽ được gọi constructor mặc định của nó.
4. Xóa mảng
Mảng động được tạo ra nhưng không được tự động hủy(Hệ điều hành thu hồi lại) nên ta phải gọi hàm hủy.
Nếu dùng malloc hay calloc để cấp phát thì hủy bằng hàm free, nếu dùng new để cấp phát thì dùng delete[].
+ Hàm free nhận vào con trỏ void, và k0 cần ép kiểu hay truyền vào kich thước. Hàm free chỉ trả lại vùng nhớ nhưng con trỏ vẫn còn chì đến vị trí vùng nhớ đó. Con trỏ này được gọi stray pointer, tức là con trỏ trỏ đế vùng nhớ rác, vì thế sau khi goi free thì nên gán con trỏ về 0.
+ Toán tử delete[] để xóa mảng và gọi destructor của mổi phần tử trước khi thực thi. Lưu ý: phải có dấu [] nếu không chỉ có phần tử đầu tiên được xóa và bị leak memory. Sử dụng delete[] để xóa cũng nên gán con trỏ về lai 0.

int* arr1 = (int*)malloc(sizeof(int) * 10);
int* arr2 = new int[10];
---
free(arr1);
arr1 = 0;
delete[] arr2;
arr2 = 0;
* Việc sử dụng malloc hay new để tạo mảng tùy thuộc vào người dùng. Hàm malloc cấp phát bộ nhớ nhanh, toán tử new cấp phát chậm hơn nhưng an toàn(đảm bảo các phần tử đều được khởi tạo bằng default constructor).

Saturday, August 8, 2015

Mảng trong C/C++

1. Định nghĩa mảng
Mảng là một khối các ô nhớ liên tục nhau, được chia thành nhiều cell(phần tử), mỗi cell chứa một đối tượng. Các cell sẽ được truy cập bằng chỉ một số index riêng. Cell dầu tiên có chỉ số index là 0, và tăng dần tiếp theo.
Mảng là một kiểu random-access structor bởi vì có thể truy cập bất cứ phần từ nào của mảng ngay lập tức bằng index của nó, cho dù phần tử ở vị trí nào trong mảng thì thời gian truy cập vẫn như nhau.
Có 2 kiểu mảng: mảng tỉnh(static array) và mảng động(dynamic array).
- Mảng tỉnh là mảng có kích thước không đổi.
- Mảng động là mảng có kích thước có thể thay đổi được.
2. Truy cập mảng
Truy cập mảng có thể dùng một trong các cách sau:

int* array = new int[5];   //Khoi tao mang voi do dai la 5
array[2] = 5;              //Gan gia tri 5 vo cell co index 2
array[3] = array[2];       //Lay gia tri cell co index 2 
                           //gan vao cel co index 3
Mảng thường được sử dụng như một con trỏ trỏ vào phần tử đầu tiên(index 0) của mảng. Có thể dùng với các toán tử +, -, ++, -- để trỏ đến một vị trí khác trogn mảng.

int* array = new int[5];   //Khoi tao mang, 
                           //mac dinh con tro array chi vao index 0
array++;                   //Di chuyen con tro 
                           //sang phai 1 phan tu(index 1)
array--;                   //Di chuyen con tro 
                           //sang trai 1 phan tu( ve lai index 0)
array = array + 3;         //Di chuyen con tro 
                           //sang phai 3 phan tu(index 3)
3. Khởi tạo mảng
+ Mảng tỉnh, khoirwtaoj bằng dấu ngoặc vuông []

int arr1[10];              //Tao mang tinh co 10 phan tu.
int arr2[10] = {1, 5, 9};  //Tao mang tinh co 10 phan tu, 
                           //va gan gia tri cho 3 phan tu dau.
+ Mảng động khởi tạo bằng new hoặc malloc

int* arr1 = new int[10];              //Tao mang dong co 10 phan tu.
int* arr2 = new int[10]{1, 5, 4};     //Tao mang dong co 10 phan tu, 
                                      //va gan gia tri cho 3 phan tu dau.
int* arr3 = (int*)malloc(sizeof(int)*10); //Tao mang co 10 phan tu.
Khi khởi tạo vùng nhớ chứa mảng tỉnh nằm trên stack, vùng nhớ chứa mảng động nằm trên heap. Xem new, malloc và calloc
Mảng tỉnh sau khi sử dụng sẽ tự được hủy, mảng động không tự hủy được, phải gọi hàm hủy(nếu khởi tạo bằng new thì dùng delete[], nếu khởi tạo bằng malloc thì dùng free) cho nó.
4. Truyền mảng vào hàm

void func1(int arr[])
{
}

void func2(int* arr)
{
}

func1(array);
func2(array);
2 hàm này không khác gì nhau, chỉ khác về cách khai báo, một hàm dùng dấu [], một hàm dùng dấu * như con trỏ.
Lưu ý: Kích thước(tính theo byte) của mảng tỉnh được tình bằng hàm sizeof, nhưng đối với mảng động thì không tính được, phải lưu lại kích thước của nó khi sử dụng. Mảng tỉnh khi được truyền vào hàm thì trong hàm cũng k0 lấy được kích thước, nên cũng phải truyền theo kích thước vào hàm.

Monday, June 15, 2015

Memory layout of program

Cấu trúc cơ bản của một chương trình C đang chạy trong bộ nhớ(memory) gồm các phần sau:
  • 1. Text segment(code segment)
  • 2. Initialized data(data segment)
  • 3. Uninitialize data(bss segment)
  • 4. Heap
  • 5. Stack

1. Text segment(Code segment)
Text segment(.text segment) hay còn gọi là Code segment là phần bộ nhớ chứa code đã được compile của chương trình, vùng nhớ này thường là read-only. Phần text segment này lấy từ text segment trong file thực thi excutable object.
Thông thường text segment là sharable memory, nó chỉ tồn tại duy nhất một bản copy trên physic memory dùng chung cho tất cả các instance của chương trình. Ví dụ như chương trình soạn thảo văn bản, có thể mở nhiều instance cùng lúc, nhiều process được tạo ra, nhưng chỉ là trên địa chỉ ảo(virtual address) của process, còn thực tế thì chỉ có một bản copy duy nhất trên physic memory.
2. Initalized data segement
Initialized data segment (.data hay còn gọi là Data segment) là vùng nhớ này chứa các biến global, static hoặc extern được khởi tạo trực tiếp trong code bởi lập trình viên(hard-code). Vùng nhớ này có thể được chia làm 2 loại: read-only và read-write. Và có thể một số cấu trúc chia segment này ra thành 2 segment: .data(initialized data) và .rdata(read-only initialized data).
Ví dụ:

char  s1[]  = "Hello world";
char* s2    = "Hello world";
s1[0] = 'A';  //Not error
s2[0] = 'A';  //ERROR

- s1 là non-const array, nghĩa là một mảng chứa 12 character bao gồm cả \0 ở cuối được khởi tạo trong vùng nhớ read-write. Vì s1 là mảng nên địa chỉ của s1 với địa chỉ phần tử đầu tiên của s1 là như nhau(xuất log %p của s1 hay của &s1 đều như nhau). Data của s1 nằm trong vùng nhớ read-write nên được phép thay đổi giá trị.
- s2 là non-const pointer to const data, nghĩa là con trỏ trỏ tới một vùng data(trỏ tới character đầu tiên) và vùng data này nằm trong vùng read-only. s2 chỉ là con trỏ nên địa chỉ của s2 khác với địa chỉ của &s2(&s2 là lấy địa chỉ của địa chỉ hay con trỏ cấp 2). Data của s2 nằm trong vùng nhớ read-only nên không thể thay đổi giá trị.
- Cả 2 data này đều không thể thay đổi kích thước, chỉ có data trên stack và heap là thay đổi được kích thước.

char  s1[]  = "Hello world";
char* s2    = "Hello world";
s1[]  = "Hello world 2";   //ERROR
s2    = "Hello world 2";   //Trỏ con trỏ sang vùng nhớ khác 
                           //chứ không phải thay đổi data ở vùng nhớ củ

3. Uninitialized data segment
Uninitialized data segment (.bss hay còn gọi là BSS segment) là vùng nhớ chứa các biến global, static hoặc extern chưa được khởi tạo trong code và sẽ được khởi tạo bằng 0 khi chương trình bắt đầu thực thi. Các biến này không chiếm bộ nhớ trên object file, mà nó chỉ là một place holder.
4. Stack segment
Stack là vùng nhớ được dùng để chứa các biến local, các biến được truyền đi(passing argument) khi thực thi một hàm và địa chỉ của giá trị trả về sau khi thực thi hàm. Các biến local chỉ tồn tại trong một block code mà nó được định nghĩa, khi ra khỏi block các biến này sẽ được xóa khỏi stack. Các giá trị được thêm vào stack theo nguyên tắc LIFO theo hướng từ địa chỉ cao xuống đia chỉ thấp(trên kiến trúc x86, có thể theo chiều ngược lại ở một số kiến trúc khác).
Thanh nhớ stack pointer sẽ ghi nhớ lại đỉnh của stack mổi khi có giá trị thêm vào. Khi một bộ các giá trị được đẩy vào để thực thi một hàm ta gọi là một stack frame. Một stack frame có ít nhất một địa chỉ trả vể(chứa địa chỉ của giá trị trả về sau khi gọi hàm). Tất cả các hàm gọi lồng nhau được thêm vào stack thành nhiều stack frame. Xem cách hàm được thực thi.
5. Heap segment
Heap là vùng nhớ được cấp phát động bởi các lênh malloc, realloc và free. Vùng nhớ heap được cấp phát mở rộng từ vùng nhớ thấp đến vùng nhớ cao(ngược lại với stack). Stack và heap mở rộng đến khi "đụng" nhau là lúc bộ nhớ cạn kiệt. Vùng nhớ heap là vùng nhớ share giữa các tất cả các shared library và dynamic library được load trong process(tiến trình).

Vùng nhớ của một tiến trình được quản lý bằng địa chỉ ảo(virtual address) nên sẽ liên tục nhau, nhưng thực tế sẽ được ánh xạ không liên tục trên RAM.

Monday, June 1, 2015

Xem code assembly trong Visual Studio

Chức năng xem code assembly của Visual Studio chỉ chạy khi cờ tạo thông tin debug được bật.
- Chỉnh project ở chế độ Debug.
- Đặt break point ở vị trí muốn xem code asm. Chạy project, Visual Studio sẽ dừng lại ở vị trí break point.
- Nhấp phải chuột, chọn Go To Deassembly. Nội dung code asm sẽ hiện ra.
- Code asm sinh ra có thể tùy vào thông tin debug. Có thể chỉnh sang các mode build /Z7, /Zi, /Zl. Ở mode /Zl thông tin debug được tạo ra đầy đủ nên trong code asm sẽ có cả code debug.
Ví dụ: code của một hàm C++ và code asm được sinh ra ở mode /Z7