Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 52.126 phút đọc

Vòng đời đối tượng

Sau bài này bạn sẽ làm được

  • Kể bốn thời gian lưu trữ và ví dụ cho từng cái
  • Giải thích vì sao in địa chỉ một biến đã chết là UB
  • Nhận ra con trỏ treo trong mã thật
  • Dùng _Thread_local đúng chỗ

Chuẩn C không nói về "biến toàn cục" hay "biến trên ngăn xếp". Nó nói về thời gian lưu trữ, và có đúng bốn loại. Hiểu chúng ở tầng chuẩn giải thích được những chỗ mà cách nghĩ theo ngăn xếp và heap không giải thích nổi.

#Bốn thời gian lưu trữ

Thời gian lưu trữSống từ khi nào tới khi nàoKhai báo thế nào
Tĩnh (static)Trước khi main chạy tới khi chương trình kết thúcBiến ở phạm vi tệp, hoặc có từ khóa static
Luồng (thread)Từ khi luồng bắt đầu tới khi luồng kết thúc_Thread_local, từ C11
Tự động (automatic)Từ khi vào khối tới khi ra khỏi khốiBiến cục bộ không có static
Cấp phát (allocated)Từ malloc tới freemalloc, calloc, realloc, aligned_alloc
Bốn loại trong một tệp
#include <stdlib.h>
#include <threads.h>

int g;                       /* TINH: pham vi tep */
static int s;                /* TINH: pham vi tep, lien ket trong */
_Thread_local int tls;       /* LUONG */

void f(void) {
    int a;                   /* TU DONG: chet khi f tra ve */
    static int b;            /* TINH: du nam trong ham */

    {
        int c;               /* TU DONG: chet khi ra khoi khoi nay */
    }

    int *p = malloc(4);      /* CAP PHAT: song toi khi free */
    free(p);                 /* chet o day */
}
Vòng đời (lifetime)
Khoảng thời gian mà một đối tượng chắc chắn có bộ nhớ dành riêng cho nó và giữ giá trị cuối cùng được lưu vào. Ngoài khoảng đó, mọi thao tác với nó là hành vi không xác định.

#Đọc con trỏ treo cũng là UB

terminal
gcc -std=c11 -O1 -Wall -Wextra -o vongdoi.exe vongdoi.c && ./vongdoi.exe
g[0] = 1
p[0] = 0  (con tro treo)
q     = 000000000061FE40  (doc dia chi cung la UB)
Trình biên dịch khai thác điều đó
#include <stdio.h>

int main(void) {
    int *p, *q;

    { int x = 0; p = &x; }
    { int y = 0; q = &y; }

    /* Hai bien co the dung CHUNG mot cho tren ngan xep, vi vong doi
       cua chung khong chong nhau. Nen p == q co the DUNG. */

    if (p == q) printf("bang nhau\n");   /* co the in, co the khong */

    return 0;
}

/* GCC va Clang cho ket qua khac nhau o day, va cung mot trinh bien dich
   o hai muc toi uu khac nhau cung co the khac.

   Do la vi so sanh hai con tro treo la UB, nen khong co cau tra loi dung. */

#Năm dạng con trỏ treo

Một: trả về địa chỉ biến cục bộ

int *f(void) {
    int x = 42;
    return &x;               /* -Wreturn-local-addr bat duoc */
}

/* Bai 33.3 da do: GCC o -O2 thay luon dia chi do bang NULL. */

Hai: con trỏ ra khỏi khối

int *p;
if (dieu_kien) {
    int tam = tinh();
    p = &tam;                /* tam chet o dau ngoac dong */
}
dung(p);                     /* UB */

/* GCC bat duoc voi -Wdangling-pointer tu ban 12,
   nhung GCC 8.1 thi khong. */

Ba: compound literal trong hàm

static int *tra_ve_cl(void) {
    return (int[]){10, 20, 30};   /* thoi gian song TU DONG */
}

/* Bai 51.2 da do: p[0] cho ra 0 thay vi 10.
   Bai 52.4 se noi ky. */

Bốn: con trỏ sau free

free(p);
if (p != NULL) ...           /* UB: doc gia tri cua p */

/* Chua bang cach gan NULL ngay sau free: */
free(p);
p = NULL;                    /* gia tri MOI, hop le */
if (p != NULL) ...           /* gio hop le */

Năm: con trỏ vào vùng đã realloc

int *a = malloc(10 * sizeof *a);
int *p = &a[5];                   /* con tro vao giua */

int *moi = realloc(a, 20 * sizeof *a);
if (moi != NULL) a = moi;

/* Bay gio "p" TREO, va ca "a" cu cung treo neu realloc doi cho.

   Chuan noi: neu realloc thanh cong, doi tuong cu ket thuc vong doi,
   va moi con tro toi no tro nen bat dinh.

   Nen: khong bao gio giu con tro vao giua mot vung co the realloc.
   Giu CHI SO thay vi con tro: */

size_t i = 5;
a = realloc(a, ...);
a[i]                              /* van dung */

#_Thread_local

#include <threads.h>          /* cho thread_local, ten ngan */

_Thread_local int dem;        /* moi luong mot ban rieng */
thread_local int dem;         /* tu C11 qua threads.h, cung nghia */

/* Ket hop voi static hoac extern: */
static _Thread_local int rieng;      /* lien ket trong */
extern _Thread_local int chung;      /* lien ket ngoai */

/* Trong ham thi phai co static hoac extern: */
void f(void) {
    static _Thread_local int x;      /* dung */
    _Thread_local int y;             /* error: cannot be applied to
                                        block scope variable without
                                        static or extern */
}

#Bốn quy tắc sở hữu

Sở hữu không rõ ràng
/* Ai giai phong cai nay? Tai lieu khong noi. */
char *lay_ten(void);

/* Con tro nay song bao lau? Khong noi. */
const char *sv_ten(const SinhVien *sv);

/* Ham nay co giu con tro lai khong? Khong noi. */
void dang_ky(const char *ten);
Sở hữu ghi rõ trong tài liệu
/* Nguoi goi SO HUU, phai goi free */
char *lay_ten(void);

/* MUON: song toi khi sv bi huy hoac sv_dat_ten duoc goi.
   Nguoi goi KHONG duoc free, va phai sao chep neu muon giu lau */
const char *sv_ten(const SinhVien *sv);

/* Ham nay SAO CHEP chuoi, nguoi goi giu quyen so huu cua minh */
void dang_ky(const char *ten);

/* Va ghi thang vao tai lieu cua header, khong chi trong dau ban. */
Ba quy ước đặt tên phổ biến
/* Quy uoc 1: hau to noi ro */

char *ml_ten_moi(const SinhVien *sv);      /* "_moi": ban so huu */
const char *ml_ten(const SinhVien *sv);    /* khong hau to: ban muon */

/* Quy uoc 2: kieu tra ve noi ro */

char       *lay_ten(void);        /* char *: thuong la ban so huu */
const char *xem_ten(void);        /* const char *: thuong la ban muon */

/* Quy uoc 3: tham so ra, nguoi goi cap bo dem */

int ml_ten_vao(const SinhVien *sv, char *ra, size_t co);
/* Khong ai cap phat gi ca. Ro rang nhat, va thuong la tot nhat
   cho thu vien C. */

/* Va mot mau rat huu ich: hoi kich thuoc truoc */

size_t n = ml_ten_vao(sv, NULL, 0);     /* tra ve so byte can */
char *bo_dem = malloc(n + 1);
ml_ten_vao(sv, bo_dem, n + 1);

/* Do la cach snprintf va nhieu ham he thong lam. */

Tự làm thử

  1. Viết một tệp có đủ bốn thời gian lưu trữ và giải thích từng cái sống bao lâu.
  2. In địa chỉ một biến đã ra khỏi phạm vi và đối chiếu với phụ lục J.2.
  3. So sánh hai con trỏ treo và chạy ở hai mức tối ưu khác nhau.
  4. Viết năm dạng con trỏ treo và xem cờ cảnh báo nào bắt được cái nào.
  5. Dùng _Thread_local cho một bộ đệm lỗi và kiểm thử với hai luồng.
  6. Viết lại một header của bạn với chú thích sở hữu cho mọi hàm trả về con trỏ.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Bốn thời gian lưu trữ: tĩnh, luồng, tự động, cấp phát. Chuẩn không nhắc tới ngăn xếp hay heap.
  • static trong hàm đổi thời gian lưu trữ chứ không đổi phạm vi.
  • Chuẩn nói dùng giá trị của con trỏ treo là UB, không chỉ giải tham chiếu. Kể cả in nó ra.
  • realloc thành công làm mọi con trỏ vào vùng cũ trở nên bất định, nên giữ chỉ số thay vì con trỏ.
  • Mỗi con trỏ có đúng một chủ sở hữu, và quyền sở hữu phải ghi vào tài liệu của header.