Việc sử dụng _start () trong C là gì?


125

Tôi học được từ đồng nghiệp của mình rằng một người có thể viết và thực thi một chương trình C mà không cần viết một main()hàm. Nó có thể được thực hiện như thế này:

my_main.c

/* Compile this with gcc -nostartfiles */

#include <stdlib.h>

void _start() {
  int ret = my_main();
  exit(ret); 
}

int my_main() {
  puts("This is a program without a main() function!");
  return 0; 
}

Biên dịch nó bằng lệnh này:

gcc -o my_main my_main.c nostartfiles

Chạy nó bằng lệnh này:

./my_main

Khi nào người ta cần làm loại điều này? Có bất kỳ kịch bản thế giới thực nào mà điều này sẽ hữu ích không?



7
Bài viết cổ điển trình bày một số hoạt động bên trong của cách các chương trình khởi động: Hướng dẫn tạo Whirlwind về việc tạo các bảng thực thi ELF thực sự thiếu niên cho Linux . Đây là một bài đọc hay thảo luận về một số điểm tốt hơn của _start()và những thứ khác bên ngoài main().

1
Bản thân ngôn ngữ C không nói gì về _start, hoặc về bất kỳ điểm nhập nào khác ngoài main(ngoại trừ tên của điểm nhập được định nghĩa triển khai cho các triển khai tự do (nhúng)).
Keith Thompson

Câu trả lời:


107

Biểu tượng _startđiểm đầu vào của chương trình của bạn. Đó là, địa chỉ của biểu tượng đó là địa chỉ được chuyển đến khi bắt đầu chương trình. Thông thường, hàm có tên _startđược cung cấp bởi một tệp có tên crt0.ochứa mã khởi động cho môi trường thời gian chạy C. Nó thiết lập một số nội dung, điền vào mảng đối số argv, đếm có bao nhiêu đối số ở đó và sau đó gọi main. Sau khi maintrả lại, exitđược gọi.

Nếu một chương trình không muốn sử dụng môi trường thời gian chạy C, nó cần phải cung cấp mã riêng cho _start. Ví dụ, việc triển khai tham chiếu của ngôn ngữ lập trình Go làm như vậy bởi vì chúng cần một mô hình phân luồng không chuẩn, đòi hỏi một số phép thuật với ngăn xếp. Nó cũng hữu ích để cung cấp của riêng _startbạn khi bạn muốn viết các chương trình thực sự nhỏ hoặc các chương trình làm những điều độc đáo.


2
Một ví dụ khác là trình liên kết / tải động của Linux có _start được định nghĩa riêng.
PP

2
@BlueMoon Nhưng điều đó cũng _startđến từ tệp đối tượng crt0.o.
fuz

2
@ThomasMatthews Tiêu chuẩn không chỉ định _start; trong thực tế, nó không xác định những gì xảy ra trước đó mainđược gọi là gì, nó chỉ xác định những điều kiện phải được đáp ứng khi mainđược gọi. Đó là một quy ước cho điểm nhập cảnh có _starttừ ngày xưa.
fuz

1
"việc triển khai tham chiếu của ngôn ngữ lập trình Go làm như vậy bởi vì chúng cần một mô hình phân luồng không chuẩn" crt0.o là C cụ thể (thời gian chạy crt-> C). Không có lý do gì để mong đợi nó được sử dụng cho bất kỳ ngôn ngữ nào khác. Và mô hình luồng Go là hoàn toàn phù hợp tiêu chuẩn
Steve Cox

8
@SteveCox Nhiều ngôn ngữ lập trình được xây dựng dựa trên thời gian chạy C vì việc triển khai ngôn ngữ theo cách này dễ dàng hơn. Go không sử dụng mô hình phân luồng thông thường. Họ sử dụng các ngăn xếp nhỏ, được phân bổ theo đống và bộ lập lịch của riêng họ. Đây chắc chắn không phải là một mô hình phân luồng tiêu chuẩn.
fuz

45

Trong khi mainlà điểm vào cho chương trình của bạn từ góc độ lập trình viên, _startlà điểm vào thông thường từ góc độ Hệ điều hành (lệnh đầu tiên được thực thi sau khi chương trình của bạn được khởi động từ Hệ điều hành)

Trong một chương trình C điển hình và đặc biệt là C ++, rất nhiều công việc đã được thực hiện trước khi việc thực thi vào main. Đặc biệt là những thứ như khởi tạo các biến toàn cục. Ở đây bạn có thể tìm thấy lời giải thích tốt về mọi thứ đang diễn ra giữa _start()main()và cả sau khi main đã thoát trở lại (xem bình luận bên dưới).
Mã cần thiết cho điều đó thường được cung cấp bởi người viết trình biên dịch trong tệp khởi động, nhưng với cờ –nostartfilesvề cơ bản bạn nói với trình biên dịch: "Đừng bận tâm đến việc cung cấp cho tôi tệp khởi động tiêu chuẩn, hãy cho tôi toàn quyền kiểm soát những gì đang xảy ra ngay từ khởi đầu".

Điều này đôi khi cần thiết và thường được sử dụng trên các hệ thống nhúng. Ví dụ: nếu bạn không có hệ điều hành và bạn phải kích hoạt thủ công một số phần nhất định của hệ thống bộ nhớ (ví dụ: bộ nhớ đệm) trước khi khởi tạo các đối tượng chung của bạn.


Các vars chung là một phần của phần dữ liệu và do đó được thiết lập trong quá trình tải chương trình (nếu chúng là const thì chúng là một phần của phần văn bản, cùng một câu chuyện). Hàm _start hoàn toàn không liên quan đến điều đó.
Cheiron

@Cheiron: Xin lỗi, emistake của tôi Trong c ++, các biến toàn cục thường được khởi tạo bởi một hàm tạo được chạy bên trong _start()(hoặc thực tế là một hàm khác được gọi bởi nó) và trong nhiều Bare-Metal-Programs, bạn sao chép rõ ràng tất cả dữ liệu toàn cục từ flash vào RAM đầu tiên, điều này cũng xảy ra trong _start(), nhưng câu hỏi này không phải về c ++ hay mã bare-metal.
MikeMB

1
Lưu ý rằng trong một chương trình tự cung cấp _start, thư viện C sẽ không được khởi tạo trừ khi bạn tự thực hiện các bước đặc biệt - có thể không an toàn khi sử dụng bất kỳ chức năng không an toàn tín hiệu không đồng bộ nào từ chương trình như vậy. (Không có đảm bảo chính thức rằng bất kỳ chức năng thư viện nào sẽ hoạt động, nhưng các chức năng không đồng bộ-tín hiệu-an toàn không thể tham chiếu đến bất kỳ dữ liệu toàn cầu nào, vì vậy chúng sẽ phải cố gắng hết sức.)
zwol

@zwol điều đó chỉ đúng một phần. Ví dụ, một hàm như vậy có thể cấp phát bộ nhớ. Việc phân bổ bộ nhớ có vấn đề khi cấu trúc dữ liệu bên trong mallockhông được khởi tạo.
fuz

1
@FUZxxl Đã nói rằng, tôi nhận thấy rằng các chức năng không đồng bộ-tín hiệu-an toàn được phép sửa đổi errno(ví dụ: không readđồng writebộ-tín hiệu-an toàn và có thể được đặt errno) và đó có thể là một vấn đề tùy thuộc vào chính xác thời errnođiểm vị trí mỗi luồng được phân bổ .
zwol

2

Dưới đây là tổng quan tốt về những gì xảy ra trong quá trình khởi động chương trình trước đó main . Đặc biệt, nó cho thấy đó __startđiểm truy cập thực tế vào chương trình của bạn từ quan điểm hệ điều hành.

Đây là địa chỉ đầu tiên mà từ đó con trỏ hướng dẫn sẽ bắt đầu đếm trong chương trình của bạn.

Mã ở đó gọi một số quy trình thư viện thời gian chạy C chỉ để thực hiện một số công việc dọn phòng, sau đó gọi cho bạn main, sau đó mang mọi thứ xuống và gọi exitvới bất kỳ mã thoát nào được maintrả về.


Một bưc tranh đang gia ngan lơi noi:

Sơ đồ khởi động thời gian chạy C


Tái bút: câu trả lời này được ghép từ một câu hỏi khác mà SO đã đóng lại một cách hữu ích là bản sao của câu này.


Đăng chéo để lưu giữ các phân tích xuất sắc và hình ảnh đẹp.
ulidtko

1

Khi nào người ta cần làm loại điều này?

Khi bạn muốn mã khởi động của riêng mình cho chương trình của bạn.

mainkhông phải là mục đầu tiên cho một chương trình C, _startlà mục đầu tiên sau bức màn.

Ví dụ trong Linux:

_start: # _start is the entry point known to the linker
    xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
    mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
    lea 8(%rsp), %rsi         # take the address of argv from the stack
    lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
    xor %eax, %eax            # per ABI and compatibility with icc
    call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main

    mov %eax, %edi    # transfer the return of main to the first argument of _exit
    xor %eax, %eax    # per ABI and compatibility with icc
    call _exit        # terminate the program

Có bất kỳ kịch bản thế giới thực nào mà điều này sẽ hữu ích không?

Nếu bạn muốn nói, hãy thực hiện riêng của chúng tôi _start:

Có, trong hầu hết các phần mềm nhúng thương mại mà tôi đã làm việc, chúng tôi cần phải triển khai phần mềm riêng của chúng tôi _startliên quan đến các yêu cầu hiệu suất và bộ nhớ cụ thể của chúng tôi.

Nếu bạn muốn, hãy bỏ mainchức năng và thay đổi nó thành một cái gì đó khác:

Không, tôi không thấy lợi ích gì khi làm điều đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.