Toán tử lấy địa chỉ
Sau bài này bạn sẽ làm được
- Dùng toán tử lấy địa chỉ đúng cách
- Xác định kiểu của biểu thức lấy địa chỉ
- Biết những gì không lấy địa chỉ được và vì sao
- In địa chỉ đúng định dạng
Toán tử lấy địa chỉ là toán tử đơn giản nhất trong C: nó trả lời câu hỏi biến này nằm ở đâu. Bài này ngắn, nhưng nó chốt lại một chi tiết mà nếu không nắm thì mọi bài về con trỏ sau đây đều mù mờ: kiểu của kết quả.
#Cú pháp và ý nghĩa
& đặt trước tên biến, cho ra địa chỉ của biến đó. Đây là toán tử một ngôi, khác hoàn toàn với toán tử và trên bit cũng viết là & nhưng có hai toán hạng.int n = 42;
&n /* địa chỉ của n */
printf("%p\n", (void *)&n); /* in ra ví dụ 0x7ffd3a2c1e4c */Bạn đã dùng toán tử này từ Bài 3.3 mà chưa biết tên nó. Mọi lời gọi scanf đều cần nó.
int n;
scanf("%d", &n); /* đưa cho scanf địa chỉ của n để nó ghi vào */
scanf("%d", n); /* SAI: đưa giá trị rác của n, scanf coi đó là địa chỉ */Toán tử này áp dụng được cho gì
int n;
int a[5];
struct S { int x; } s;
&n /* biến đơn */
&a[2] /* một phần tử của mảng */
&a /* cả mảng, kiểu khác hẳn, xem Bài 13.4 */
&s /* một struct */
&s.x /* một trường của struct */Điều kiện chung là toán hạng phải là một vùng nhớ có tên, thuật ngữ chuẩn gọi là lvalue. Mục dưới liệt kê những thứ không thỏa điều kiện đó.
#Kiểu của kết quả
Đây là chi tiết quyết định. Kết quả của &x không phải là một số nguyên chung chung, mà là một con trỏ có kiểu cụ thể.
| Khai báo biến | Biểu thức | Kiểu của kết quả |
|---|---|---|
| int n; | &n | int * |
| double d; | &d | double * |
| char c; | &c | char * |
| int a[5]; | &a[0] | int * |
| int a[5]; | &a | int (*)[5] |
| struct S s; | &s | struct S * |
| int *p; | &p | int ** |
#include <stdio.h>
int main(void)
{
int n = 42;
double d = 3.14;
int *pn = &n; /* đúng: int * nhận int * */
double *pd = &d; /* đúng */
int *sai = &d; /* cảnh báo: gán double * cho int * */
return 0;
}kieu.c:11:19: warning: initialization of 'int *' from incompatible pointer type 'double *' [-Wincompatible-pointer-types]
#Những gì không lấy địa chỉ được
| Thứ | Ví dụ | Vì sao không lấy được |
|---|---|---|
| Hằng số | &42 | Số 42 không nằm ở đâu cả, nó là một giá trị trong lệnh máy |
| Biểu thức | &(a + b) | Kết quả nằm trong thanh ghi, không có địa chỉ bộ nhớ |
| Biến register | &r với register int r | Chuẩn cấm, vì biến đó có thể nằm hẳn trong thanh ghi |
| Trường bit của struct | &s.co | Trường bit không chiếm nguyên byte nên không có địa chỉ |
| Giá trị trả về của hàm | &f() | Kết quả lời gọi hàm không phải vùng nhớ có tên nên không có địa chỉ |
&42; /* error: lvalue required as unary '&' operand */
&(a + b); /* lỗi tương tự */
register int r;
&r; /* error: address of register variable requested */
struct { unsigned co : 1; } s;
&s.co; /* error: cannot take address of bit-field */#In địa chỉ đúng cách
printf("%d\n", &n); /* sai kiểu, và cắt cụt trên máy 64 bit */
printf("%p\n", &n); /* thiếu ép kiểu, hành vi không xác định */
printf("%lu\n", &n); /* cũng sai kiểu */printf("%p\n", (void *)&n); /* cách chuẩn */
#include <inttypes.h>
printf("%" PRIxPTR "\n", (uintptr_t)&n); /* khi cần in dạng số nguyên */| Kiểu | Định dạng | Ghi chú |
|---|---|---|
| void * | %p | Cách chuẩn, in dạng cài đặt tự chọn |
| uintptr_t | PRIxPTR trong inttypes.h | Kiểu nguyên đủ rộng chứa một con trỏ, có từ C99 |
| ptrdiff_t | %td | Hiệu hai con trỏ, dùng ở Bài 12.8 |
#include <inttypes.h>
#include <stdio.h>
int main(void)
{
int n = 42;
printf("dang chuan : %p\n", (void *)&n);
printf("dang so nguyen: %" PRIuPTR "\n", (uintptr_t)&n);
printf("dang thap luc : 0x%" PRIxPTR "\n", (uintptr_t)&n);
return 0;
}dang chuan : 0x7ffd3a2c1e4c dang so nguyen: 140725579423308 dang thap luc : 0x7ffd3a2c1e4c
#Ba chỗ dễ nhầm với toán tử khác
Nhầm 1: và một ngôi so với và hai ngôi
int a = 12, b = 10;
int *p = &a; /* một ngôi: lấy địa chỉ */
int c = a & b; /* hai ngôi: phép và trên từng bit, cho ra 8 */Trình biên dịch phân biệt bằng số toán hạng. Nếu có thứ gì đứng bên trái dấu & thì đó là phép toán bit, ngược lại là lấy địa chỉ.
Nhầm 2: và trong khai báo tham chiếu
int &r = a; /* C++ có tham chiếu, C KHÔNG CÓ. Đây là lỗi cú pháp trong C. */Nếu bạn đọc mã C++ hoặc tài liệu C++, dấu & trong khai báo có nghĩa hoàn toàn khác. C không có tham chiếu, và đó là lý do C bắt buộc dùng con trỏ ở những chỗ C++ dùng tham chiếu.
Nhầm 3: và và trong biểu thức logic
if (p != NULL && *p > 0) /* && là và logic, hai dấu và */
if (a & b) /* & là và trên bit, một dấu */Tự làm thử
- In địa chỉ của năm biến kiểu khác nhau bằng cả ba định dạng trong bảng.
- Cố tình quên dấu và trong
scanf, đọc cảnh báo, rồi chạy và quan sát chương trình sập. - Gán
&dcủa mộtdoublevàoint *, đọc cảnh báo, rồi ép kiểu và in giá trị nhận được. - Thử lấy địa chỉ của cả năm thứ trong bảng không lấy được, chép lại thông báo lỗi của từng cái.
- In
&avà&a[0]của cùng một mảng, xác nhận giá trị giống nhau rồi insizeofcủa cả hai và giải thích chênh lệch. - Viết chương trình có cả bốn cách dùng dấu và trong bài kiểm tra nhỏ, in kết quả từng dòng.
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
- Toán tử
&một ngôi cho ra địa chỉ của một vùng nhớ có tên. - Nếu
xcó kiểuTthì&xcó kiểuT *. Kiểu của con trỏ quyết định cách đọc vùng nhớ đó. - Hằng số, biểu thức, biến register và trường bit đều không lấy địa chỉ được, vì chúng không phải vùng nhớ có tên.
- In địa chỉ bằng
%pvà luôn ép về(void *). - Dấu
&có hai nghĩa tùy số toán hạng: một ngôi là lấy địa chỉ, hai ngôi là phép và trên bit.