Tại sao phải dùng new khi đã khai báo con trỏ

hàm new dùng để cấp phát bộ nhớ cho con trỏ, nhưng con trỏ khi ta khai báo thì nó đã có vùng nhớ hay địa chỉ rồi. Tại sao vẫn cấp phát bộ nhớ cho nó trong danh sách liên kết ạ?

1 Like
  • Con trỏ khi ta khai báo thì nó đã có vùng nhớ hay địa chỉ rồi -> CHÍNH XÁC
    • Cụ thể hơn là biến con trỏ được cấp phát trên Stack
    • Vùng nhớ được cấp là 32bit (Win32), 64bit (Win64)
  • int *p = new int[10];
    • new int[10] là xin cấp phát 10 ô nhớ để chứa giá trị int
    • 10 ô nhớ đó nằm liền kề nhau trên Heap
    • p = Đia chỉ ô nhớ đầu tiên trong 10 ô nhớ đó
    • Theo cách khai báo biến tĩnh (int a, b, c;) bạn sẽ phải khai báo 10 biến cho 10 ô nhớ
    • Nhưng với cách cấp phát động(int *p = new int[10];), để truy cập tới các ô nhớ khác bạn chỉ việc *(p +1) cho ô nhớ thứ 2, hay *(p +4) cho ô nhớ thứ 5
  • Việc chỉ dùng 1 biến có thể quản lý được cả trăm ô nhớ, so với việc phải khai báo cả trăm biến là một lợi ích của con trỏ
  • https://cpp.daynhauhoc.com/8/0-con-tr/ (Bạn tham khảo bài này của anh Đạt để nắm rõ hơn con trỏ nhé)
4 Likes
typedef struct node {
	int Data;
	node *pNext;
}Node;

typedef struct {
	Node *pHead;
	Node *pTail;
}List;

void Init(List &l) {
	l.pHead = l.pTail = NULL;
}

Node* GetNode(int x) {		
	Node *p = new Node;		
	if (p == NULL)
		return NULL;
	p->Data = x;			
	p->pNext = NULL;		
	return p;
}

em không hiểu tại sao *p = new Node ạ và trên struct Node tại sao phải có Node *pNext, vì khi Node này trỏ đến Node kia thì chỉ cần khai báo *pNext là con trỏ trỏ đi 1 Node khác rồi ạ
tại e mới vào cộng đồng này nên e còn chưa biết nhiều và cũng chỉ mới học ạ, mong anh chị chỉ bảo nhiều

1 Like

dslk với các node nằm trên stack cũng được, nhưng chỉ tạo được dslk có kích thước cố định trước khi chạy, chả khác gì mảng tĩnh cả. Để tạo dslk có kích thước tùy biến thì phải cấp phát động cho mỗi node của nó.

vd

Node node5{55, nullptr};
Node node4{44, &node5};
Node node3{33, &node4};
Node node2{22, &node3};
Node node1{11, &node2};
List list{&node1, &node5};

list cũng là dslk, các node ko cần cấp phát động, nhưng để thêm node mới ở runtime thì ko có cách nào tạo node6 trên stack hết, nên mới cần cấp phát động.

ví dụ thêm n node mới cho dslk tĩnh (ko thêm được)

int n; cin >> n;
for (int i = 0; i < n; ++i)
{
    Node newNode{0, nullptr};
    cin >> newNode.data;
    if (list.pTail)
        list.pTail = list.pTail->next = &newNode; //sai
    else
        list.pHead = list.pTail = &newNode; //cũng sai
} //vì bắt đầu vòng lặp mới thì newNode sẽ bị xóa vì nó được khai báo trên stack,
  //thoát khỏi phạm vi của nó là bị xóa ngay, nên &newNode ko còn trỏ tới đúng
  //node mới thêm nữa.
2 Likes

nghĩa là sao ạ, Node *p = new Node dòng lệnh này nghĩa là cấp phát cho dslk động phải không ạ, câu lệnh này nghĩa là nó tự tạo ra 1 Node mới để chúng ta thêm dữ liệu vào ạ

con trỏ chỉ là 1 số nguyên chứa địa chỉ của vùng nhớ nào đó thôi, ko phải là 1 vùng nhớ :V

1 Like

nó cũng là 1 vùng nhớ để chứa 1 địa chỉ nào đó chứ, con trỏ cũng có địa chỉ nó cũng dc 1 con trỏ khác trỏ đến nó, nó có giá trị là địa chỉ của 1 biến khác và nó cũng là 1 vùng nhớ phải không ạ
và e vẫn không hiểu câu lệnh Node *p = new Node ạ, mong anh chị chỉ giúp

Node* p = new Node là tạo 1 Node trên heap và gán địa chỉ của node vừa tạo vô p thôi. p chỉ là 1 số nguyên. Nói cách khác là tạo 1 căn nhà ở bãi đất heap, rồi trả về địa chỉ của nó là số 123. p là mẩu giấy chứa số 123 đó, ko phải căn nhà :V

còn khai báo Node q; nghĩa là xây 1 căn nhà tại chỗ tôi đang đứng, và tôi dịch 4m, căn nhà ở kế bên tôi có tên là q :V

nhà tạo trên bãi đất heap là do anh quản lý, khi nào anh muốn đập nó thì anh gọi delete, còn nhà trên bãi stack của tôi thì khi thoát khỏi dấu } là tôi tự động đập. Vì thế muốn dslk thêm bớt node được theo yêu cầu khi chạy thì phải tạo node trên heap.

nó y hệt khai báo Node q; thôi. Thay vì q là node thì trong Node* p = new Node; *p là node. Và *p nằm trên heap, khi nào muốn giải phóng thì giải phóng, ko bị ông stack pointer ổng tự động càn quét như q.

4 Likes
  • Có lẻ bạn đang hiểu lầm giữa địa chỉ của biến con trỏ và giá trị địa chỉ mà biến con trỏ chứa.
    :rofl::rofl::rofl::rofl: Câu ở trên mình ghi mà mình còn thấy nó khó hiểu :sweat_smile::sweat_smile::sweat_smile::sweat_smile:
  • Biến con trỏ trong hệ điều hành 32 bit, sẽ có kích cỡ là 32 bit.
    • Như vậy 1 chương trình sử dụng con trỏ 32 bit sẽ quản lý được tối đa 2^32 ô nhớ (từ ô nhớ thứ 0 đến ô nhớ thứ 2^32 - 1)
    • Với mỗi ô nhớ là 1 bit thì 2^32bit = 4Gb
    • Như vậy hệ điều hành Windows 32 bit chỉ tối đa 4Gb Ram !!!
  • Như vậy bản thân biến con trỏ 32 bit cũng là 1 biến Integer 32 bit thôi, số Interger chứa trong biến con trỏ chính là thứ tự 1 ô nhớ trong Ram
1 Like

có nghĩa là 1 con trỏ sẽ là 32Bit khi dùng trên máy 32Bit và khi dùng xong nó lại tự cấp phát 32Bit cho con trỏ 2 ạ

cho e hỏi heap là gì ạ :smile:

  • Cấp phát bộ nhớ tĩnh (Static memory allocation)

    • Ví dụ : int a; hay float b = 8.8; hay char c = 'h';
    • Cấp phát bộ nhớ tĩnh nghĩa là vùng nhớ(ô nhớ trong ram) sẽ được xin cấp phát lúc biên dịch chương trình.
    • Xin cấp phát nghĩa là xin hệ điều hành cho phép sử dụng ô nhớ đó, chương trình được phép đọc, ghi lên ô nhớ đó
    • Vậy làm sao để biết ô nhớ đó ở đâu mà đọc, ghi ? Nhờ tên biến (a, b ,c) !
    • Khi nào bị hủy cấp phát (không được đọc ghi lên ô nhớ nữa), Khi biến ra khỏi phạm vi của biến ?
    • {{int a;} cout << a;} đoạn chương trình trên bị lỗi vì a đã bị hủy cấp phát
    • Phạm vi biến nằm trong khoảng { }
  • Cấp phát bộ nhớ động (Dynamic memory allocation)

    • int *a = new int; hay Node *p = new Node();
    • Cấp phát bộ nhớ động nghĩa là vùng nhớ(ô nhớ trong ram) sẽ được xin cấp phát lúc chạy chương trình.
    • Vậy làm sao để biết ô nhớ đó ở đâu mà đọc, ghi ? Nhờ tên biến giá trị địa chỉ mà biến con trỏ chứa
    • * là toán tử lấy giá trị tại địa chỉ ô nhớ
    • int a =2; cout << *a; đoạn chương trình trên in giá trị của ô nhớ thứ 2 trong Ram, nhưng bị lỗi do ta chưa xin phép sử dụng ô nhớ thứ 2 (xin phép bằng toán tử new)
    • Khi nào bị hủy cấp phát ? Khi tao dùng toán tử delete delete p;
  • Ngoài ra Ram chia ra nhiều phân vùng

    • Heap (Cấp phát động xin ô nhớ ở đây)
    • Stack (Cấp phát tỉnh xin ô nhớ ở đây)
    • Còn nhiều nữa nha
  • Hiểu hơn và Heap, Stack bạn tham khảo https://cpp.daynhauhoc.com/8/4-cap-phat-bo-nho-dong/

  • Ngoài ra phân vùng Heap và Stack trên Ram không liên quan gì đến Cấu trúc dữ liệu Stack và Heap

1 Like

heap là cái tên thôi em :upside_down_face: stack thì có tên là stack nghĩa là “chồng” như chồng tập do phần tử sau nằm kề sát phần tử trước, cả vùng nhớ này ko có lỗ hở hay ko có vùng nhớ nào ko được sử dụng nằm giữa nó, y như cái chồng tập. Còn heap nghĩa là 1 đống bừa bộn, chỗ thì có xài, chỗ thì ko xài, như 1 đống bừa bộn vậy nên gọi là heap :V

2 Likes

Giả sử bạn có 1 memory có 16 cell. Mỗi cell có 4 bits. Memory lưu tối đa là 8 bytes.
Địa chỉ (address) từng cell đánh số từ 0 (0000) tới 15 (1111) (theo nhị phân).
Stack (S) chiếm 4 cell từ 0000 đến 0011.
Heap (H) chiếm phần còn lại là 12 cell, từ 0100 tới 1111.

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
- - - - - - - - - - - - - - - -

Note: ("-" biểu thị giá trị chưa biết)

Memory quy định như sau:

  • Số nguyên int được cấp phát 2 cell, nên giá trị int được lưu 1 byte.
  • Kiểu int*, pointer to int, lưu trữ address của int. Vì address <= 1111 nên address có 4 bits, nên chỉ cần có 1 cell để lưu trữ int*.

Bước 1: Khai báo biến i1.

int i1 = 2;

i1 là biến kiểu int, nên memory cho i1 2 cell 00000001, giá trị là 00000010

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
0000 0010 - - - - - - - - - - - - - -

Bước 2: Khai báo pointer p2.

int *p2 = &i1;

p2 kiểu int*, memory cấp cho p2 1 cell 0010, có giá trị là address của i1, hay 0000

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
0000 0010 0000 - - - - - - - - - - - - -

Bước 3: Khai báo p3.

int* p3 = new int;

p3 kiểu int*, được khởi tạo bằng new int. Lúc này có 2 bước con:

  • memory cung cấp 2 cell liên tiếp bất kì trên Heap, thay vì Stack, giả sử là 10001001.
  • memory gán giá trị 1000, là địa chỉ đầu tiên của cell đã cung cấp do new int, vào cell 0011.
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
0000 0010 0000 1000 - - - - - - - - - - - -

Bước 4:

*p3 = 5;

Lệnh này có các bước con:

  • Tìm address của p3, là 0011.
  • Lấy giá trị trong cell 0011, được giá trị khác là 1000.
  • Tìm tới cell 10001001 (vì p3int*), gán giá trị 00000101.
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
0000 0010 0000 1000 - - - - 0000 0101 - - - - - -

Bước 5: sử dụng delete

delete p3;

delete để giải phóng số nguyên đã cấp phát cho p3. Lúc này không quan tâm đến giá trị trong cell 10001001 nữa. Hai giá trị trở về “-”.

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
0000 0010 0000 1000 - - - - - - - - - - - -

Nhưng thực chất nó để y xì như ban đầu.

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
S S S S H H H H H H H H H H H H
0000 0010 0000 1000 - - - - 0000 0101 - - - - - -

int i1 = 2;
int *p2 = &i1;
int* p3 = new int;
*p3 = 5;
delete p3;

Cơ bản là vậy. :penguin:
Muốn hiểu chi tiết thì học thêm môn Operating Systems.

(Mục đích chính: tập viết markdown cho đẹp)

5 Likes

dùng new thì tạo 1 vùng nhớ trên heap , nhưng để dùng thì cần địa chỉ của nó, con trỏ là để lưu địa chỉ của vùng đó , nghĩa là con trỏ có hay không thì vùng nhớ vẫn tồn tại chỉ là bạn có dùng hay không thôi(leak memory) đó là lí do c/c++ yêu cầu xóa vùng nhớ sau khi dùng còn trong java hay những ngôn ngữ bậc cao khác thì nó tự dọn được

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?