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

const là hợp đồng

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

  • Đặt const đúng chỗ cho con trỏ và cho thứ nó trỏ tới
  • Giải thích vì sao ép bỏ const rồi ghi là hành vi không xác định
  • Dùng const cho tham số hàm để nói rõ hợp đồng
  • Kết hợp const với volatile cho thanh ghi chỉ đọc

constnói hai điều với hai người khác nhau: với trình biên dịch là "hãy chặn tôi nếu tôi ghi vào đây", và với người đọc là "hàm này không sửa dữ liệu của bạn". Nó không nói với hệ điều hành rằng vùng nhớ này chỉ đọc.

#Bốn dạng khai báo

const int x = 5;              /* hang so */

const char *p;                /* con tro toi const char:
                                 KHONG sua duoc *p, sua duoc p */

char *const q = bo_dem;       /* con tro const toi char:
                                 sua duoc *q, KHONG sua duoc q */

const char *const r = ten;    /* ca hai deu khong sua duoc */
Khai báoSửa được *pSửa được pDùng khi
char *p;ĐượcĐượcBộ đệm bạn sở hữu và sửa
const char *p;KhôngĐượcTham số chỉ đọc. Trường hợp thường gặp nhất
char *const p;ĐượcKhôngCon trỏ tới thanh ghi cố định, hoặc biến cục bộ không muốn gán lại
const char *const p;KhôngKhôngBảng tra chỉ đọc

#const là hợp đồng, không phải khóa

co.c
#include <stdio.h>

int main(void) {
    const int x = 5;

    int *p = (int *)&x;      /* ep bo const: hop le ve CU PHAP */

    *p = 10;                 /* HANH VI KHONG XAC DINH */

    printf("x  = %d\n", x);
    printf("*p = %d\n", *p);
    printf("&x = %p, p = %p\n", (void *)&x, (void *)p);

    return 0;
}
terminal
gcc -std=c11 -O2 -o co co.c && ./co
x  = 5
*p = 10
&x = 000000000061FE4C, p = 000000000061FE4C
gcc -std=c11 -O0 -o co co.c && ./co
x  = 10
*p = 10
&x = 000000000061FE44, p = 000000000061FE44

#const trong tham số hàm

Không có const
size_t do_dai(char *s);
int    so_sanh(char *a, char *b);
void   in(SinhVien *sv);

/* Doc nguyen mau khong biet ham co sua du lieu cua ban khong.
   Phai doc than ham, va phai doc lai moi lan no thay doi. */
const cho mọi tham số chỉ đọc
size_t do_dai(const char *s);
int    so_sanh(const char *a, const char *b);
void   in(const SinhVien *sv);

/* Doc nguyen mau la biet ngay: ba ham nay khong sua gi ca. */
Quy tắc đặt const cho mọi loại tham số
/* Doc, khong sua */
size_t sv_dem(const SinhVien *ds, size_t n);

/* Sua noi dung, khong doi con tro */
void sv_chuan_hoa(SinhVien *sv);

/* Ghi ket qua ra, tham so ra */
int sv_doc(const char *dong, SinhVien *ra);

/* Mang chi doc */
int tim(const int a[], size_t n, int x);

/* Mang con tro chi doc, ca hai tang */
void in_het(const char *const ten[], size_t n);

/* Con tro toi con tro, tang trong chi doc */
int gom(const SinhVien *const *ds, size_t n);

#const không lan truyền qua một tầng

const không phải hằng số biên dịch

const int N = 10;

int a[N];                /* o pham vi tep: error: variably modified
                            'a' at file scope */

switch (x) { case N: }   /* error: case label does not reduce to
                            an integer constant */

/* Bai 19.2 da noi ky: trong C, const nghia la "khong duoc GHI",
   khong phai "gia tri biet luc bien dich".

   Dung enum cho so nguyen can lam hang so bien dich: */
enum { N = 10 };

int a[N];                /* dung */
switch (x) { case N: }   /* dung */

#Kết hợp với volatile

const volatile uint32_t *ID_CHIP = (const volatile uint32_t *)0x1FFF7A10;

uint32_t id = *ID_CHIP;      /* doc duoc */
*ID_CHIP = 0;                /* error: assignment of read-only location */
Khai báoChương trình ghi đượcĐọc lại mỗi lầnVí dụ thật
uint32_t xĐượcKhôngBiến thường
const uint32_t xKhôngKhôngHằng số
volatile uint32_t *pĐượcCóThanh ghi điều khiển, ví dụ GPIO ODR
const volatile uint32_t *pKhôngCóThanh ghi chỉ đọc: mã chip, cờ trạng thái, bộ đếm
Ví dụ thật từ CMSIS
typedef struct {
    volatile uint32_t CR1;        /* doc va ghi */
    volatile uint32_t CR2;
    const volatile uint32_t SR;   /* CHI DOC: co trang thai */
    volatile uint32_t DR;
    volatile uint32_t BRR;
} USART_TypeDef;

#define USART1 ((USART_TypeDef *)0x40011000UL)

while (!(USART1->SR & (1u << 7))) { }    /* doc duoc */

USART1->DR = b;                           /* ghi duoc */

USART1->SR = 0;                           /* error: read-only.
                                             Va do la dieu ban muon:
                                             thanh ghi nay xoa co bang
                                             cach doc DR, khong bang
                                             cach ghi vao SR. */

Tự làm thử

  1. Viết bốn dạng khai báo con trỏ const và thử gán để xem cái nào bị từ chối.
  2. Chạy chương trình ép bỏ const ở -O0 và -O2, so hai kết quả.
  3. Đưa biến const ra phạm vi tệp rồi chạy lại, xem chương trình sập ở đâu.
  4. Chạy nm trên tệp đối tượng và xác nhận biến const có chữ R.
  5. Viết hàm nhận const Vung * rồi sửa dữ liệu qua con trỏ bên trong, xác nhận trình biên dịch không nói gì.
  6. Khai báo một thanh ghi const volatile và thử ghi vào nó.

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

  • const áp dụng cho thứ bên trái nó, và nếu không có gì bên trái thì cho thứ bên phải.
  • const là hợp đồng với trình biên dịch, không phải bảo vệ bộ nhớ. Ép bỏ rồi ghi là hành vi không xác định.
  • Biến const ở phạm vi tệp nằm trong vùng chỉ đọc thật, nên ghi vào gây lỗi phân đoạn. Biến const trong hàm thì không.
  • const trên struct không lan sang dữ liệu mà con trỏ bên trong nó trỏ tới.
  • const volatile không mâu thuẫn: một cái nói về quyền của bạn, cái kia nói về hành vi của thế giới.