Xét đoạn code sau với 4 biến const
:
-
INIT_GLOBAL_VAR
: phạm vi global, có khởi tạo giá trị -
UNINIT_GLOBAL_VAR
: phạm vi global, không khởi tạo giá trị -
INIT_VAR
: phạm vi local, có khởi tạo giá trị -
UNINIT_VAR
: phạm vi local, không khởi tạo giá trị
#include <stdio.h>
const int INIT_GLOBAL_VAR = 123;
const int UNINIT_GLOBAL_VAR;
int main(void) {
const int INIT_VAR = 456;
const int UNINIT_VAR;
int *p = NULL;
/*
* Cách 1
*/
*(&INIT_GLOBAL_VAR) = 789;
INIT_GLOBAL_VAR = 789;
*(&UNINIT_GLOBAL_VAR) = 789;
UNINIT_GLOBAL_VAR = 789;
*(&INIT_VAR) = 789;
INIT_VAR = 789;
*(&UNINIT_VAR) = 789;
UNINIT_VAR = 789;
/*
* Cách 2
*/
p = &UNINIT_GLOBAL_VAR; // compile warning: assignment discards ‘const’ qualifier from pointer target type
*p = 789;
p = &INIT_VAR; // compile warning: assignment discards ‘const’ qualifier from pointer target type
*p = 789;
p = &UNINIT_VAR; // compile warning: assignment discards ‘const’ qualifier from pointer target type
*p = 789;
p = &INIT_GLOBAL_VAR; // runtime error: Segmentation fault
*p = 789;
return 1;
}
Xét 2 cách thay đổi sau:
- Cách 1: thay đổi trực tiếp chúng ta sẽ gặp
compile error: assignment of read-only variable
, không cần giải thích nhiều vì điều này đã được quy định rõ trong C biến const không thể thay đổi được. - Cách 2: dùng 1 con trỏ trung gian để thay đổi, con trỏ không nhất thiết phải là trỏ hằng. Cách này pass được compile error chỉ còn lại
compile warning: assignment discards ‘const’ qualifier from pointer target type
và 1runtime error: Segment fault
.
-
INIT_VAR
vàUNINIT_VAR
thay đổi được là vì đây là 2 biến local khi hàmmain
chạy thì chúng mới được khởi tạo và khởi tạo trên vùng stack (RAM region) - Biến ‘UNINIT_GLOBAL_VAR’ tuy là biến global nhưng do không được được khởii tạo giá trị nên nó thuộc về vùng
.bss
mà đây là vùng nhớ modify được, phân tích map file sẽ thấy:
.bss 0x000000000060103c 0xc
*(.dynbss)
.dynbss 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o
*(.bss .bss.* .gnu.linkonce.b.*)
.bss 0x000000000060103c 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o
.bss 0x000000000060103c 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crti.o
.bss 0x000000000060103c 0x1 /usr/lib/gcc/x86_64-linux-gnu/4.8/crtbegin.o
.bss 0x000000000060103d 0x0 /tmp/ccTcj2bs.o
.bss 0x000000000060103d 0x0 /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS)
.bss 0x000000000060103d 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.8/crtend.o
.bss 0x000000000060103d 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crtn.o
*(COMMON)
*fill* 0x000000000060103d 0x3
COMMON 0x0000000000601040 0x4 /tmp/ccTcj2bs.o
0x0000000000601040 UNINIT_GLOBAL_VAR
0x0000000000601048 . = ALIGN ((. != 0x0)?0x8:0x1)
*fill* 0x0000000000601044 0x4
- Biến
INIT_GLOBAL_VAR
là biến global và có khởi tạo giá trị nên nó được xếp vào vùngrodata
, đây là vùng nhớ đặt biệt được protect bởi hệ thống nên nếu có gắng thay đổi vùng nhớ này bằng cách nào đó thì hệ thống sẽ quăng exceptionSegment fault
như trên và dừng chương trình, phân tích map file sẽ thấy:
.rodata 0x00000000004005e0 0x8
*(.rodata .rodata.* .gnu.linkonce.r.*)
.rodata.cst4 0x00000000004005e0 0x4 /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o
0x00000000004005e0 _IO_stdin_used
.rodata 0x00000000004005e4 0x4 /tmp/ccTcj2bs.o
0x00000000004005e4 INIT_GLOBAL_VAR
Kết luận
- Từ khóa
const
có 2 tác dụng, giúp compiler kiểm tra syntax khi build và báo cho compiler biết nên đặt biến nào phải đặt vào vùng read-only data. - Nếu 1 biến toàn cục không được khởi tạo giá trị lúc khai báo thì nó luôn được đặt trong vùng
.bss
bất chấp có từ khóaconst
khi khai báo, lúc nàyconst
chỉ còn tác dụng kiểm tra syntax. - Nếu bạn vẫn muốn ngoan cố thay đổi biến global
const
có khởi tạo giá trị, tham khảo thêm cách dùng__attribute__
để ép compiler đặt biến đó vào vùng nhớ khác không phải là read-only data, nhưng đến đây thì đã đi xa vấn đề ban đầu quá rồi - Việc vô tình hay cố ý thay đổi biến
const
đều không được recomment vì nó tạo ra nhữngundefined behaviour
như trong ví dụ trên làSegment fault
P/S: trong bài viết, mình dùng máy ảo của cloud9.io để test code,tùy vào từng platform và compiler mà vị trí đặt các biến ở những vùng nhớ sẽ khác nhau, bên dưới là cấu hình hệ thống và thông tin compiler mình dùng trong bài test
>> ddt_1793:~/workspace (master) $ uname -a # system information
Linux ddt_1793-test_machine-3790314 4.2.0-c9 #1 SMP Fri Nov 20 14:49:01 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
>> ddt_1793:~/workspace (master) $ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.