#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Cái này có gọi là gián tiếp mainkhông? làm sao?
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Cái này có gọi là gián tiếp mainkhông? làm sao?
Câu trả lời:
Ngôn ngữ C định nghĩa môi trường thực thi theo hai loại: tự do và được lưu trữ . Trong cả hai môi trường thực thi, một hàm được gọi bởi môi trường khởi động chương trình.
Trong môi trường tự do, chức năng khởi động chương trình có thể được xác định trong khi trong môi trường được lưu trữ, nó phải như vậy main. Không chương trình nào trong C có thể chạy mà không có chức năng khởi động chương trình trên các môi trường đã xác định.
Trong trường hợp của bạn, mainbị ẩn bởi các định nghĩa tiền xử lý. begin()sẽ mở rộng đến decode(a,n,i,m,a,t,e)mà sẽ được mở rộng hơn nữa main.
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main()
decode(s,t,u,m,p,e,d)là một macro được tham số hóa với 7 tham số. Danh sách thay thế cho macro này là m##s##u##t. m, s, uvà t4 thứ , 1 st , 3 thứ và 2 nd tham số được sử dụng trong danh sách thay thế.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
Phần còn lại không có ích gì ( chỉ để làm rối loạn ). Đối số được chuyển tới decodelà " a , n , i , m , a, t, e" vì vậy, các định danh m, s, uvà tđược thay thế bằng các đối số m, a, ivà ntương ứng.
m --> m
s --> a
u --> i
t --> n
_start(). Hoặc cấp thấp hơn nữa, tôi có thể cố gắng căn chỉnh phần bắt đầu chương trình của mình với địa chỉ mà IP được đặt sau khi khởi động. main()là thư viện C Standard . Bản thân C không áp đặt hạn chế về điều này.
decode(a,n,i,m,a,t,e)trở thành m##a##i##n? Nó có thay thế các ký tự không? Bạn có thể cung cấp liên kết đến tài liệu của decodehàm không? Cảm ơn.
beginđược định nghĩa để được thay thế bởi decode(a,n,i,m,a,t,e)cái được định nghĩa trước đó. Hàm này nhận các đối số s,t,u,m,p,e,dvà nối chúng ở dạng này m##s##u##t( ##có nghĩa là nối). Tức là, nó bỏ qua các giá trị của p, e và d. Khi bạn "gọi" decodevới s = a, t = n, u = i, m = m, nó sẽ thay thế beginbằng main.
Hãy thử sử dụng gcc -E source.c, đầu ra kết thúc bằng:
int main()
{
printf("Ha HA see how it is?? ");
}
Vì vậy, một main()hàm thực sự được tạo ra bởi bộ tiền xử lý.
Chương trình được đề cập thực hiện cuộc gọi main()do mở rộng macro, nhưng giả định của bạn là thiếu sót - nó hoàn toàn không phải gọi main()!
Nói một cách chính xác, bạn có thể có một chương trình C và có thể biên dịch nó mà không cần mainký hiệu. mainlà thứ mà đối tượng c librarymong đợi sẽ nhảy vào, sau khi nó hoàn thành quá trình khởi tạo của chính nó. Thông thường bạn nhảy vào maintừ biểu tượng libc được gọi là _start. Luôn luôn có thể có một chương trình rất hợp lệ, chỉ đơn giản thực hiện hợp ngữ mà không cần có một chương trình chính. Hãy xem này:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
Biên dịch ở trên với gcc -nostdlib without_main.cvà xem nó in Hello World!trên màn hình chỉ bằng cách đưa ra lệnh gọi hệ thống (ngắt) trong lắp ráp nội tuyến.
Để biết thêm thông tin về vấn đề cụ thể này, hãy xem blog ksplice
Một vấn đề thú vị khác là bạn cũng có thể có một chương trình biên dịch mà không cần mainký hiệu tương ứng với một hàm C. Ví dụ, bạn có thể có phần sau là một chương trình C rất hợp lệ, chương trình này chỉ làm cho trình biên dịch rên rỉ khi bạn tăng cấp Cảnh báo.
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
Các giá trị trong mảng là các byte tương ứng với các hướng dẫn cần thiết để in Hello World trên màn hình. Để có tài khoản chi tiết hơn về cách chương trình cụ thể này hoạt động, hãy xem bài đăng trên blog này , đây là nơi tôi cũng đọc nó đầu tiên.
Tôi muốn thông báo cuối cùng về các chương trình này. Tôi không biết liệu họ có đăng ký là các chương trình C hợp lệ theo đặc tả ngôn ngữ C hay không, nhưng việc biên dịch các chương trình này và chạy chúng chắc chắn là rất khả thi, ngay cả khi chúng vi phạm chính đặc tả.
_startmột phần của tiêu chuẩn đã xác định, hay đó chỉ là việc thực hiện cụ thể? Chắc chắn "chính như một mảng" của bạn là kiến trúc cụ thể. Cũng quan trọng, sẽ không vô lý nếu thủ thuật "chính dưới dạng mảng" của bạn không thành công trong thời gian chạy do các hạn chế về bảo mật (mặc dù điều đó sẽ có nhiều khả năng xảy ra hơn nếu bạn không sử dụng bộ định lượng constvà vẫn có nhiều hệ thống cho phép nó).
_startkhông có trong tiêu chuẩn ELF, mặc dù AMD64 psABI có chứa tham chiếu đến Khởi tạo quy trình_start tại 3.4 . Về mặt chính thức, ELF chỉ biết về địa chỉ e_entrytrong tiêu đề ELF, _startchỉ là một cái tên mà triển khai đã chọn.
constsẽ không quan trọng một chút nào - tên biểu tượng trong tệp thực thi nhị phân đó là main. Không nhiều không ít. constlà một cấu trúc C có nghĩa là không có gì tại thời điểm thực thi.
Ai đó đang cố gắng hành động như Magician. Anh ta nghĩ rằng anh ta có thể lừa chúng tôi. Nhưng chúng ta đều biết, việc thực thi chương trình c bắt đầu bằng main().
Các int begin()sẽ được thay thế bằng decode(a,n,i,m,a,t,e)bằng một đường chuyền của giai đoạn tiền xử lý. Sau đó, một lần nữa, decode(a,n,i,m,a,t,e)sẽ được thay thế bằng m ## a ## i ## n. Như bằng liên kết vị trí của lệnh gọi macro, ssẽ có giá trị là ký tự a. Tương tự như vậy, usẽ được thay thế bằng 'i' và tsẽ được thay thế bằng 'n'. Và, đó là cách, m##s##u##tsẽ trở thànhmain
Về, ##biểu tượng trong mở rộng macro, nó là toán tử tiền xử lý và nó thực hiện dán mã thông báo. Khi macro được mở rộng, hai mã thông báo ở hai bên của mỗi toán tử '##' được kết hợp thành một mã thông báo duy nhất, sau đó thay thế cho '##' và hai mã thông báo ban đầu trong mở rộng macro.
Nếu bạn không tin tôi, bạn có thể biên dịch mã của mình với -Ecờ. Nó sẽ dừng quá trình biên dịch sau khi xử lý trước và bạn có thể thấy kết quả của việc dán mã thông báo.
gcc -E FILENAME.c
decode(a,b,c,d,[...])xáo trộn bốn đối số đầu tiên và kết hợp chúng để nhận một số nhận dạng mới, theo thứ tự dacb. (Ba đối số còn lại bị bỏ qua.) Ví dụ: decode(a,n,i,m,[...])đưa ra số nhận dạng main. Lưu ý rằng đây là những gì beginmacro được định nghĩa.
Do đó, beginmacro được định nghĩa đơn giản là main.
Trong ví dụ của bạn, main()hàm thực sự có mặt, vì beginlà một macro mà trình biên dịch thay thế bằng decodemacro, lần lượt được thay thế bằng biểu thức m ## s ## u ## t. Sử dụng mở rộng macro ##, bạn sẽ đạt được maintừ decode. Đây là một dấu vết:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
Đó chỉ là một thủ thuật cần có main(), nhưng việc sử dụng tên main()cho hàm nhập của chương trình là không cần thiết trong ngôn ngữ lập trình C. Nó phụ thuộc vào hệ điều hành của bạn và trình liên kết là một trong những công cụ của nó.
Trong Windows, bạn không phải lúc nào cũng sử dụng main(), nhưng đúng hơn là WinMainhoặcwWinMain , mặc dù bạn có thể sử dụng main(), ngay cả với chuỗi công cụ của Microsoft . Trong Linux, người ta có thể sử dụng _start.
Nó phụ thuộc vào trình liên kết như một công cụ của hệ điều hành để thiết lập điểm nhập, chứ không phải bản thân ngôn ngữ. Bạn thậm chí có thể đặt điểm vào của riêng chúng tôi, và bạn có thể tạo một thư viện cũng có thể thực thi được !
main()hàm với ngôn ngữ lập trình C, câu này không đúng.