Mọi chương trình bạn chạy từ terminal đều có thể nhận đối số: gcc -Wall hello.c -o hello truyền vào bốn đối số. Chương trình C của bạn đọc đối số của nó qua hai tham số của main, và cơ chế chỉ có vậy: không cần gọi thư viện, không cần thiết lập gì.
Chữ ký hàm
Cho đến giờ mọi ví dụ trong bộ tài liệu này đều dùng int main(void). Dạng chuẩn còn lại nhận dòng lệnh:
int main(int argc, char *argv[]) {
/* ... */
}
argc("argument count") là số lượng đối số, tính cả tên chương trình.argv("argument vector") là một mảng chuỗi.char *argv[]nghĩa là "mảng các con trỏ tới char", tức một mảng các chuỗi C.
char **argv là một cách viết tương đương mà bạn cũng sẽ gặp thường xuyên; tham số kiểu mảng suy biến thành con trỏ, nên hai cách khai báo này là một.
Dùng bảng Đối số của trình soạn thảo để nhập đối số (mỗi ô là một đối số), rồi nhấn Chạy. Thử ba ô: hello, world, 42.
Cấu tạo
Chạy ./greet Ada Lovelace cho ra:
| Chỉ số | Giá trị | |
|---|---|---|
argv[0] | "./greet" | tên chương trình như lúc được gọi |
argv[1] | "Ada" | đối số thật đầu tiên |
argv[2] | "Lovelace" | đối số thật thứ hai |
argv[3] | NULL | phần tử kết thúc |
argc | 3 | số lượng, tính cả argv[0] |
Bốn điều đáng ghi nhớ:
- Trên thực tế
argcít nhất là 1, vìargv[0]là tên chương trình. Các đối số thật bắt đầu từ chỉ số 1, đó là lý do các vòng lặp duyệt chúng bắt đầu từi = 1. - Mọi thứ đều là chuỗi.
./prog 42cho bạn"42", tức hai ký tự, không bao giờ là số 42. Việc chuyển đổi là việc của bạn. argv[argc]làNULL. Tiêu chuẩn đảm bảo điều này, nên nếu muốn bạn có thể duyệt mảng mà không cầnargc:for (char **p = argv + 1; *p != NULL; p++).- Shell tách các từ, không phải chương trình của bạn.
./prog "Ada Lovelace"là một đối số; dấu nháy được shell xử lý và không bao giờ đến đượcargv.
Kiểm tra trước khi đọc
Một đối số không được truyền vào không phải là chuỗi rỗng, mà là vùng nhớ bạn không có quyền chạm vào. Luôn kiểm tra argc trước:
Chương trình nhỏ đó có hai quy ước, đều là chuẩn chung của các công cụ Unix:
- Thông báo hướng dẫn sử dụng được ghi ra
stderr, không phảistdout, để không làm bẩn đầu ra đang được chuyển (pipe) sang nơi khác. - Giá trị trả về khác 0 từ
mainbáo hiệu thất bại cho bất cứ thứ gì đã chạy chương trình.0nghĩa là thành công; mọi giá trị khác nghĩa là đã có lỗi. Các script shell kiểm tra giá trị này.
In argv[0] thay vì một cái tên viết cứng giúp thông báo khớp với cách người dùng gọi chương trình, kể cả khi nó đã bị đổi tên hay được gọi qua một đường dẫn khác.
Chuyển đổi đối số dạng số
argv[1] là văn bản. Muốn tính toán, bạn phải chuyển đổi nó, và hàm nên dùng là strtol trong <stdlib.h>:
Thêm 10, 20, 30 vào ba ô Đối số, sau đó đổi một ô thành banana để xem nhánh xử lý lỗi.
Tham số thứ ba của strtol là cơ số: 10 cho hệ thập phân, 16 cho hệ thập lục phân, hoặc 0 để tự nhận diện từ tiền tố 0x hay số 0 đứng đầu. Tham số thứ hai là nơi lưu con trỏ tới ký tự đầu tiên nó không đọc, và chính điều này giúp kiểm tra lỗi được:
end == textnghĩa là nó không đọc được gì: đối số không bắt đầu bằng một số.*end != '\0'nghĩa là còn văn bản thừa sau con số, nên"12abc"bị từ chối thay vì âm thầm trở thành 12.errno == ERANGEnghĩa là giá trị đã tràn khỏilong.
So sánh với atoi, ngắn hơn nhưng không báo được bất kỳ điều nào ở trên:
int n = atoi(argv[1]); /* "banana" -> 0, "0" -> 0, overflow -> undefined */
atoi trả về 0 với đầu vào không hợp lệ, không thể phân biệt với một số 0 thật. Hãy dùng strtol (và strtod cho số thực) mỗi khi đầu vào đến từ bên ngoài chương trình. Xem thêm ở bài chuyển đổi chuỗi.
Một máy tính nhỏ
Ghép tất cả lại: một chương trình nhận một số, một toán tử và một số:
Thử 12 + 30, rồi 7 x 6, rồi 5 / 0.
Chú ý strcmp(op, "+") == 0 thay vì op == "+". So sánh các giá trị char * bằng == là so sánh địa chỉ chứ không phải nội dung, và sẽ cho kết quả sai ngay cả với hai đoạn văn bản giống hệt nhau. Xem các hàm xử lý chuỗi.
Dùng x thay cho * là một chi tiết thực tế: hầu hết các shell mở rộng một dấu * đứng riêng thành danh sách các tệp trong thư mục hiện tại trước khi chương trình của bạn kịp chạy. Đặt nó trong dấu nháy ('*') cũng được, nhưng chọn một ký tự mà shell bỏ qua thì thân thiện hơn.
Xử lý các cờ tùy chọn
Theo quy ước, các tùy chọn bắt đầu bằng -. Một vòng lặp tự viết đơn giản xử lý được các trường hợp thường gặp:
Thử với bốn ô Đối số: -v, -n, 3, hello.
Phép kiểm tra i + 1 >= argc trước argv[++i] là dòng quan trọng: một tùy chọn cần giá trị phải xác nhận rằng giá trị đó thật sự có mặt, nếu không -n ở vị trí đối số cuối cùng sẽ đọc vượt quá cuối mảng. Trên các hệ Unix thật, getopt trong <unistd.h> làm tất cả việc này cho bạn, kể cả các tùy chọn ngắn viết gộp như -vn3; nó rất đáng dùng khi chương trình có nhiều hơn hai hoặc ba cờ.
Đối số trong trình soạn thảo trên trình duyệt
Các khối trình soạn thảo trên trang này được biên dịch và chạy giúp bạn, và bảng Đối số là nơi bạn nhập những gì sẽ đứng sau tên chương trình trong terminal. Mỗi ô bạn thêm bằng Thêm đối số trở thành đúng một phần tử trong argv. Không có shell nào ở giữa, nên không có gì bị tách theo dấu cách và không cần dấu nháy: một ô chứa hello world được nhận như một đối số duy nhất hello world. Để truyền -n 3 hello, hãy thêm ba ô. Để trống bảng này thì argc == 1, đó là lý do điều kiện chặn argc < 2 kích hoạt ngay ở lần chạy đầu tiên, và cũng là lý do nó đáng được viết.
Các lỗi thường gặp
- Đọc
argv[1]mà không kiểm traargc. Undefined behavior khi không có đối số nào được truyền vào, và là nguyên nhân crash phổ biến nhất trong phần này. - Bắt đầu vòng lặp từ
i = 0. Như vậy tên chương trình bị xử lý như thể nó là dữ liệu. - Coi
argv[1]là một số. Nó là văn bản cho đến khi bạn chuyển đổi. - Dùng
atoivới đầu vào của người dùng. Không có cách nào phân biệt một đối số sai với số 0. - So sánh chuỗi bằng
==. Hãy dùngstrcmp. - Ghi vào các chuỗi trong
argv. Tiêu chuẩn cho phép sửa chúng nhưng vùng nhớ có giới hạn và phụ thuộc nền tảng; hãy sao chép sang bộ đệm riêng nếu bạn cần thay đổi gì đó.
Câu hỏi thường gặp
argc và argv trong C là gì?
Đó là hai tham số của int main(int argc, char *argv[]). argc là số lượng đối số dòng lệnh, tính cả tên chương trình; argv là mảng chứa các đối số đó dưới dạng chuỗi. argv[0] là tên chương trình, argv[1] là đối số thật đầu tiên, và argv[argc] luôn là NULL.
Làm sao truyền đối số cho một chương trình C?
Gõ chúng sau tên tệp thực thi trong terminal: ./program hello 42. Khi đó argc == 3, với argv[1] là chuỗi "hello" và argv[2] là chuỗi "42". Trong trình soạn thảo trên trang này, đối số được nhập ở bảng Đối số thay vì terminal.
Làm sao chuyển một đối số dòng lệnh thành int trong C?
Dùng strtol: char *end; long n = strtol(argv[1], &end, 10); rồi kiểm tra rằng end không còn trỏ vào đầu chuỗi (không đọc được gì) và *end là '\0' (không có ký tự thừa phía sau). atoi ngắn hơn nhưng trả về 0 cho cả "0" lẫn "banana", nên nó không thể báo lỗi.
argv[0] trong C là gì?
Là cái tên mà chương trình được gọi bằng, thường là ./program hoặc đường dẫn đầy đủ. Nó hữu ích trong thông báo hướng dẫn sử dụng (fprintf(stderr, "usage: %s FILE\n", argv[0])) để thông báo khớp với cách người dùng thật sự gọi chương trình. Trong vài trường hợp hiếm nó có thể là chuỗi rỗng, nên đừng mặc định là nó luôn có.