Nhưng mà em truyền tham chiếu địa chị của h vào mà anh
Tại sao cần phải cấp phát vùng nhớ cho 1 node mới trong linked list
Node new = &arr;
thì new
trỏ tới arr
là biến local int arr[3]
mà. Sau đó em gán c->head = new
nghĩa là h.head
sẽ trỏ tới arr[3]
này.
mảng arr[3]
sẽ bị thu hồi khi ra khỏi hàm AddToCollection
, nên truy cập h.head->item
là truy cập tới vùng nhớ arr[3]
đã bị thu hồi đó nên sẽ bị undefined behavior, nghĩa là chuyện gì cũng có thể xảy ra. In ra 10 cũng có thể, seg fault cũng có thể :V Em muốn viết code chạy 1 tỷ lần đúng y 1 tỷ lần hay 1 tỷ lần có 10 lần bị lỗi :V
a chạy code nó in ra số “ngẫu nhiên” ko nè: https://rextester.com/PSKE75612
đáng lẽ phải seg fault, nó in ra ngẫu nhiên thế này biết đâu mà debug :V :V đó cũng là 1 cái kém của C :V vì nó cần tốc độ cao nên nó ko check lỗi truy cập vùng nhớ đã được giải phóng mà để cho hệ điều hành xử lý cái truy cập vùng nhớ bị giải phóng này :V
thì kệ dev c :V hoặc tốt hơn là bỏ dev c chứ sao :V :V
có thể thêm flag -fsanitize=address
vào compiler cho nó check nhưng compiler cũ thì ko có flag này :V
hay em thử addtocollection 10 lần số 11 22 33 44 … rồi in ra tất cả các phần tử trong h
xem, bảo đảm ko đúng hết
đây a bị báo lỗi đây:
cấp phát động thì ok:
có -fsanitize=address
:
================================================================
==1==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffedf0c2dd8 at pc 0x000000401235 bp 0x7ffedf0c2d80 sp 0x7ffedf0c2d78
WRITE of size 8 at 0x7ffedf0c2dd8 thread T0
#0 0x401234 in AddToCollection /app/example.c:20
#1 0x401234 in main /app/example.c:28
#2 0x7f63896580b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2)
#3 0x4012dd in _start (/app/output.s+0x4012dd)
Address 0x7ffedf0c2dd8 is located in stack of thread T0 at offset 72 in frame
#0 0x4010ff in main /app/example.c:25
This frame has 2 object(s):
[32, 40) 'h' (line 26)
[64, 76) 'arr' (line 15) <== Memory access at offset 72 partially overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /app/example.c:20 in AddToCollection
Shadow bytes around the buggy address:
0x10005be10560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be10570: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be10580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be10590: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be105a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10005be105b0: 00 00 f1 f1 f1 f1 00 f2 f2 f2 00[04]f3 f3 00 00
0x10005be105c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be105d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be105e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be105f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10005be10600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==1==ABORTING
nó báo lỗi memory access ở biến ‘arr’ ngay :V :V
tên lỗi là “stack-buffer-overflow”, nghĩa là nó truy cập vùng nhớ nằm ngoài (overflow) stack :V
ủa hình như ko phải :V :V nó báo lỗi này là do xử lý int[3]
như là struct t_node
, nếu sửa lại là
struct t_node arr;
thì nó ko báo lỗi nữa, có fsanitize=address
cũng ko thấy báo lỗi :V :V :V
nhưng nếu sửa code lại thành
#include<stdio.h>
struct t_node
{
int item;
struct t_node *next;
};
typedef struct t_node *Node;
struct collection
{
Node head;
};
typedef struct collection collect;
void AddToCollection(collect *c, int item)
{
struct t_node arr;
//Node new; // CASE 1 khong ra kq gi
Node new = &arr; //CASE 2 in ra 10
//Node new = malloc(sizeof(struct t_node));// CASE 3 in ra 10
new->item = item;
new->next = c->head;
c->head = new;
}
int main()
{
collect h;
int a = 10;
AddToCollection(&h, 10);
AddToCollection(&h, 11);
AddToCollection(&h, 22);
AddToCollection(&h, 33);
AddToCollection(&h, 44);
printf("%d",h.head->item);
printf("%d",h.head->next->item);
printf("%d",h.head->next->next->item);
printf("%d",h.head->next->next->next->item);
printf("%d",h.head->next->next->next->next->item);
}
nó cũng chạy ko báo lỗi nhưng trên godbolt nó in ra 4444444444
:V còn ở rextester thì nó vẫn báo lỗi invalid memory reference :V
chứng tỏ 1 vài OS nó ko kiểm tra stack hay sao í :V biến arr
local tuy bị thu hồi nhưng giá trị nó chứa lúc trước khi bị thu hồi vẫn ko bị thay đổi sau khi thu hồi.
edit nữa: =]] nãy thả flag -g
nó ko báo lỗi, nhưng để flag -O2
nó lại báo lỗi mới lạ :V :V
================================================================
==1==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffe26a60060 at pc 0x00000040121e bp 0x7ffe26a60010 sp 0x7ffe26a60008
READ of size 4 at 0x7ffe26a60060 thread T0
#0 0x40121d in main /app/example.c:29
#1 0x7f8708ec50b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x240b2)
#2 0x4012dd in _start (/app/output.s+0x4012dd)
Address 0x7ffe26a60060 is located in stack of thread T0 at offset 64 in frame
#0 0x4010ff in main /app/example.c:25
This frame has 2 object(s):
[32, 40) 'h' (line 26)
[64, 80) 'arr' (line 15) <== Memory access at offset 64 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
lần này thì báo đúng lỗi “stack-use-after-scope” nghĩa là sử dụng biến trên stack sau khi đã ra khỏi phạm vi (đã bị thu hồi)
em đang bị confused quá anh ạ. Tại giờ em nhớ lại kiểu hàm swap truyền tham chiếu
void swap(int *a, int *b){
int tmp = *a;
*a = *b;
*b = tmp;
}
thì nó lại swap đc
vì *b = tmp
là nó gán giá trị của tmp
chứ đâu phải gán địa chỉ của biến tmp
:V
à rồi em hiểu rồi anh ạ tại phần này nhiều biến quá em loạn mấu chốt là c->head = new; anh nhỉ nên nó khác swap
ờm :V dễ loạn lắm :V :V
hay mở excel ra cho dễ hình dung =]] ô A1 chứa giá trị 10 chẳng hạn thì 10 là giá trị còn A1 là địa chỉ =] gán head = &arr thì đồng nghĩa gán head = A1, còn *b = tmp kia là gán *b = 10.
vậy anh cho em hỏi chút ạ nếu không cấp phát bộ nhớ động thì đơn giản là mình chỉ cần cho biến new trỏ đến 1 biến global là được hả anh
global được nhưng 1 collection có 10 phần tử chả lẽ 10 pt cùng trỏ tới 1 biến global :V
cái em nghĩ là như kiểu “static” linked list nghĩa là dslk biết trước số lượng phần tử, thì có thể tạo cấp phát động 1 lần cái mảng N phần tử cho dslk này luôn, ko cần cấp phát N lần mỗi lần 1 phần tử :V
Tại thầy em đang yêu cầu không được dùng cấp phát động để quản lý 20 phần tử dùng linked list. Vậy em tạo 1 mảng 20 phần tử global đc không anh.
ờm được :V mà tốt hơn là để mảng 20 phần tử đó trong struct collection luôn
struct collection {
struct t_node arr[20];
Node head;
};
thì dslk này có thể liên kết arr[0] -> arr[10] -> arr[19] -> arr[1] -> ... -> NULL
cũng được :V
Em chưa hiểu ý anh đoạn này lắm.
Chắc có gì để em thử làm xem sao. Chắc lúc đó mới dễ hình dung hơn
a code vd luôn cho :V do khó giải thích chắc đọc code dễ hình dung hơn :V
#include<stdio.h>
typedef struct t_node
{
int item;
struct t_node *next;
} node_t;
typedef node_t * p_node;
typedef struct t_collection
{
node_t arr[20];
p_node head;
int size;
} collection_t;
void AddToCollection(collection_t *c, int item)
{
if (c->size >= sizeof(c->arr)) {
perror("Collection can only hold up to 20 elements");
exit(1);
}
p_node new = &c->arr[c->size];
++c->size;
new->item = item;
new->next = c->head;
c->head = new;
}
int main()
{
collection_t h;
h.head = NULL;
h.size = 0;
int a = 10;
AddToCollection(&h, 11);
AddToCollection(&h, 22);
AddToCollection(&h, 33);
AddToCollection(&h, 44);
printf("%d",h.head->item);
printf("%d",h.head->next->item);
printf("%d",h.head->next->next->item);
printf("%d",h.head->next->next->next->item);
}
p_node new = &c->arr[c->size];
là new
trỏ tới phần tử thứ size
trong arr
, size
khởi tạo là 0 nghĩa là phần tử đầu tiên trong arr
. Sau đó tăng size
lên 1 đơn vị nghĩa là số phần tử trong c
tăng lên 1 đơn vị do đang add thêm 1 phần tử vào c
:V Do cố định 20 phần tử nên thêm
if (c->size >= sizeof(c->arr)) {
perror("Collection can only hold up to 20 elements");
exit(1);
}
để kiểm tra trước khi thêm phần tử vào :V Nếu ko muốn dừng chương trình sớm thì có thể sửa void AddToCollection
thành int AddToCollection
trả về 1 biến int
, giá trị 0
nghĩa là ko add được thêm phần tử, 1
là add thành công 1 phần tử chẳng hạn rồi để ở dưới xử lý trường hợp ko add được thêm phần tử :V
khai báo biến arr[20]
thuộc Collection
thì bảo đảm các phần tử trong Collection h
chỉ ra khỏi scope khi h
ra khỏi scope, ko bị lỗi truy cập use-after-scope kia nữa :V
Dạ thank anh ạ. Nhờ anh chỉ mà em hiểu thêm nhiều về con trỏ, hàm và dslk ạ!
Ủa làm gì có thu hồi biến local? Biến local được cấp phát trên stack, xài xong thì nó dịch stack pointer ngược lên, giá trị và địa chỉ của biến local vẫn tiếp tục được giữ nguyên cho tới khi bị thằng khác overwrite lên chứ?
có bổ sung/đính chính thêm đó :V :V
thì dịch ngược lên là thu hồi bộ nhớ trên stack đó rồi đó. Truy cập nó là UB :V
biến local mà trỏ tới vùng nhớ cấp phát động thì đương nhiên vùng nhớ cấp phát động đó ko được thu hồi, chỉ có biến con trỏ local đó bị thu hồi thôi. Nói biến local bị thu hồi đúng rồi còn gì
Cái này do là bạn khai báo ở trên kiểu Node là 1 kiểu con trỏ, do đó cần phải khai báo trước khi sử dụng, nếu không là nó sẽ bị lỗi (hoặc là không lỗi nhưng sẽ không ra kết quả). Bạn thấy dưới này tôi chạy đoạn code của bạn complier sẽ bắt lỗi và dừng ngay, nhưng nếu bạn dùng code block thì những lỗi này sẽ không bị bắt và chạy êm ru, mặc dù chẳng ra gì cả.