Menu

Đối số dòng lệnh trong C: argc, argv và cách xử lý an toàn

Cách một chương trình C đọc dòng lệnh của nó: cấu tạo của argc và argv, duyệt qua các đối số, argv[0] chứa gì, chuyển chuỗi thành số bằng strtol thay vì atoi, và một máy tính nhỏ kết hợp tất cả.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

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]NULLphần tử kết thúc
argc3số 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 42 cho 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ần argc: 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 được argv.

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ải stdout, để 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ừ main báo hiệu thất bại cho bất cứ thứ gì đã chạy chương trình. 0 nghĩ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 == text nghĩ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 == ERANGE nghĩa là giá trị đã tràn khỏi long.

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 tra argc. 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 atoi vớ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ùng strcmp.
  • 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ó.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu