Định nghĩa hàm
Sau bài này bạn sẽ làm được
- Viết định nghĩa hàm đầy đủ bốn thành phần
- Giải thích quy tắc một định nghĩa và lỗi multiple definition
- Dùng static để giới hạn hàm trong một file
- Biết khi nào nên dùng static inline thay cho macro
Nguyên mẫu là lời hứa, định nghĩa là phần thực hiện lời hứa đó. Bài này đi hết bốn thành phần của một định nghĩa, quy tắc chỉ được định nghĩa một lần, và hai từ khóa hay đi kèm là static với inline.
#Bốn thành phần
double dien_tich_hinh_tron(double ban_kinh)
{
return 3.14159265358979 * ban_kinh * ban_kinh;
}| Thành phần | Trong ví dụ | Ghi chú |
|---|---|---|
| Kiểu trả về | double | Ghi void nếu hàm không trả về gì |
| Tên hàm | dien_tich_hinh_tron | Nên là động từ hoặc cụm danh từ mô tả kết quả |
| Danh sách tham số | (double ban_kinh) | Ghi (void) nếu không nhận tham số nào |
| Thân hàm | { ... } | Luôn có ngoặc nhọn, kể cả khi chỉ một câu lệnh |
int cong(int a, int b) { return a + b; }
void chao(void) { printf("Xin chao\n"); }
double nghich_dao(double x) { return 1.0 / x; }
char chu_hoa(char c) { return c >= 'a' && c <= 'z' ? c - 32 : c; }#Kiểu trả về
Kiểu trả về quyết định chỗ đặt kết quả ở mức mã máy. Trên nền tảng x86 sáu tư bit theo quy ước System V, số nguyên và con trỏ trả về qua thanh ghi rax, còn số thực trả về qua xmm0. Đó là lý do trình biên dịch bắt buộc phải biết kiểu trả về trước khi sinh mã cho lời gọi.
cong:
lea eax, [rdi+rsi] ; kết quả vào eax
retHàm void
void in_dong_ke(int n)
{
if (n <= 0)
return; /* thoát sớm, không kèm giá trị */
for (int i = 0; i < n; ++i)
putchar('-');
putchar('\n');
}Câu lệnh return; trong hàm void là cách thoát sớm rất hữu ích. Nó cho phép bạn xử lý các trường hợp đặc biệt ngay đầu hàm rồi để phần thân chính không phải lồng thêm tầng if nào.
void xu_ly(int *a, size_t n)
{
if (a != NULL) {
if (n > 0) {
for (size_t i = 0; i < n; ++i) {
/* thân thật sự nằm ở tầng thứ tư */
}
}
}
}void xu_ly(int *a, size_t n)
{
if (a == NULL) return;
if (n == 0) return;
for (size_t i = 0; i < n; ++i) {
/* thân thật sự nằm ở tầng thứ hai */
}
}Trả về kiểu gì thì hợp lý
| Hàm làm gì | Kiểu trả về nên chọn | Vì sao |
|---|---|---|
| Trả lời câu hỏi đúng sai | int hoặc bool | Từ C99 có <stdbool.h> cho true và false |
| Đếm số lượng phần tử | size_t | Không bao giờ âm, khớp với sizeof và chỉ số mảng |
| Trả về mã lỗi | int | Quy ước 0 là thành công, khác 0 là mã lỗi |
| Trả về vùng nhớ vừa cấp | con trỏ | NULL nghĩa là thất bại |
| Không có gì để trả | void | Đừng trả về int rồi luôn trả 0 cho có |
#Quy tắc một định nghĩa
int cong(int a, int b) { return a + b; }int cong(int a, int b) { return a + b; } /* cùng nội dung vẫn là lỗi *//usr/bin/ld: b.o: in function `cong': b.c:1: multiple definition of `cong'; a.o:a.c:1: first defined here
#Hàm static
Đặt static trước một định nghĩa hàm sẽ giới hạn hàm đó trong đúng file đang chứa nó. Các file khác không thấy được, không gọi được, và tên hàm không lọt ra bảng ký hiệu chung.
#include "thongke.h"
/* Hàm phụ trợ, chỉ dùng nội bộ trong file này. */
static int so_sanh(const void *x, const void *y)
{
int a = *(const int *)x;
int b = *(const int *)y;
return (a > b) - (a < b);
}
/* Hàm công khai, có khai báo trong thongke.h. */
double trung_vi(int *a, size_t n)
{
qsort(a, n, sizeof a[0], so_sanh);
return n % 2 ? a[n / 2] : (a[n / 2 - 1] + a[n / 2]) / 2.0;
}| Lợi ích của static | Giải thích |
|---|---|
| Tránh trùng tên | Hai file cùng có hàm tên so_sanh mà không xung đột lúc liên kết |
| Nói rõ đâu là giao diện | Người đọc thấy ngay hàm nào là API công khai, hàm nào là chi tiết bên trong |
| Trình biên dịch tối ưu mạnh hơn | Vì biết chắc không ai ngoài file này gọi, nó có thể nhúng thẳng hàm vào chỗ gọi hoặc xóa hàm nếu không ai dùng |
| Giảm kích thước file thực thi | Hàm static không dùng tới sẽ bị loại bỏ hoàn toàn |
0000000000000000 t so_sanh 0000000000000030 T trung_vi
#inline và khi nào dùng
Mỗi lời gọi hàm có chi phí: đẩy đối số, nhảy đi, lập khung, thu khung, nhảy về. Với hàm thân rất ngắn và bị gọi trong vòng lặp nóng, chi phí đó có thể lớn hơn cả phần việc thật. Từ khóa inline là lời gợi ý cho trình biên dịch chép thẳng thân hàm vào chỗ gọi.
static inline int lon_hon(int a, int b)
{
return a > b ? a : b;
}| Macro | static inline | |
|---|---|---|
| Kiểm tra kiểu | Không có | Có đầy đủ |
| Đánh giá đối số | Có thể nhiều lần, sinh lỗi ngầm | Đúng một lần |
| Gỡ lỗi được bằng gdb | Không | Có |
| Nằm trong phạm vi khối | Không, có hiệu lực tới cuối file | Có |
| Tốc độ sau khi tối ưu | Nhanh | Nhanh tương đương |
#define LON_HON(a, b) ((a) > (b) ? (a) : (b))
int i = 7;
int m = LON_HON(i++, 5);
/* Bung thành ((i++) > (5) ? (i++) : (5)).
Điều kiện 7 > 5 đúng nên nhánh (i++) chạy tiếp, i tăng HAI lần thành 9,
và m nhận 8 chứ không phải 7. */static inline int lon_hon(int a, int b)
{
return a > b ? a : b;
}
int i = 7;
int m = lon_hon(i++, 5); /* i tăng đúng một lần thành 8, m nhận 7 */Trường hợp static inline trong file tiêu đề là ngoại lệ hợp lệ duy nhất của quy tắc một định nghĩa: mỗi file nạp nó sẽ có một bản riêng, nội bộ, nên không xung đột lúc liên kết.
Tự làm thử
- Viết
void in_bang_cuu_chuong(int n)in bảng nhân của n từ 1 tới 10, có thoát sớm khi n nằm ngoài khoảng hợp lệ. - Tạo hai file cùng định nghĩa một hàm trùng tên để tự gây ra lỗi
multiple definition, rồi sửa bằngstatic. - Chạy
nmlên file đối tượng của bạn, đối chiếu chữ T và chữ t với các hàm có và không cóstatic. - Viết macro
BINH_PHUONG(x)rồi gọi nó vớii++. Giải thích kết quả nhận được, sau đó viết lại bằngstatic inline. - Dùng
gcc -Sđể xem mã hợp ngữ của một hàm cộng đơn giản khi biên dịch với-O0và với-O2. Đếm số lệnh của mỗi bả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
- Định nghĩa hàm gồm kiểu trả về, tên, danh sách tham số và thân hàm.
- Trong hàm
void, câu lệnhreturn;đứng một mình dùng để thoát sớm và làm thân hàm phẳng hơn. - Mỗi hàm chỉ được định nghĩa đúng một lần trong cả chương trình. File tiêu đề chỉ nên chứa khai báo.
statictrước hàm giới hạn nó trong một file, giúp tránh trùng tên và cho trình biên dịch tối ưu mạnh hơn.static inlinean toàn hơn macro về kiểu và về số lần đánh giá đối số, nhưng chỉ nên dùng khi đã đo được lợi ích.