入口函数
C的入口函数是main,C++的入口函数也是这个。这是相对于C程序来说,对于C/C++运行时来说,入口函数其实是mainCRTStartup函数,main实际上应该称为user entry函数。
入口函数形式
入口函数名称指定为main,main函数形式有多种:主要表现在main函数参数以及返回值类型
函数参数和返回值类型可以任意组合。但参数要么没有,要么是int argc, char** argv形式。返回值类型要么是void,要么是int类型。
无参无返回
void main() {
}
无参返回int类型
int main(){
}
有参无返回
void main(int argc, char** argv) {
}
有参返回int类型
int main(int argc, char** argv) {
}
以下面的例子为例来讲解入口函数,采用gcc编译器:
#include <stdio.h> void main() { printf("hello, c!\n"); }
这是一段简单的C程序,只有一个入口函数main函数。
编译链接:
>gcc maintest.c -o maintest
>maintest
hello, c!
指定入口函数
C程序必须要有入口函数,这个函数就是main函数。但实际上这个说法是不准确的,C程序是必须要有入口函数,但并不非得是main函数。
可以通过-e main(main为入口函数名)或者--entry=main(main为入口函数名,这种方式其实已经没用了。gcc: extraneous argument to '--entry' option)来指定入口函数
# gcc -nostdlib maintest.c -o maintest
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 00000000080480b8
/tmp/ccJd078d.o: In function `main':
maintest.c:(.text+0x11): undefined reference to `puts'
collect2: ld returned 1 exit status
# gcc -nostartfiles maintest.c -o maintest
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 00000000080481ec
这里在指定-nostartfiles后编译链接有个警告提示,这个警告提示可以通过指定-e main(main为入口函数名)或者--entry=main(main为入口函数名,这种方式其实已经没用了。gcc: extraneous argument to '--entry' option)来消除
# gcc -nostartfiles -e main maintest.c -o maintest
# ./maintest
hello, c!
Segmentation fault
执行的时候有正常输出,但最后报错:段错误(Segmentation fault)。程序还是有问题。
下面是在Windows Cygwin下的编译,编译结果表现不一样:
>gcc -nostdlib maintest.c -o maintest
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cclEIYe9.o:maintest.c:(.te
xt+0xa): undefined reference to `___main'
/cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cclEIYe9.o:maintest.c:(.te
xt+0x16): undefined reference to `_puts'
collect2: ld returned 1 exit status
>gcc -nostartfiles maintest.c -o maintest
这里在指定-nostartfiles后编译链接没报什么错。
>.\maintest
运行没有任何输出。
上面的程序例子在执行的时候会报一个段错误(Segmentation fault),这里稍微对这个程序改一下:
#include <stdio.h> #include <stdlib.h> void main() { printf("hello, c!\n"); exit(0); }
编译链接:
# gcc mainexittest.c -o mainexittest
# ./mainexittest
hello, c!
# gcc -nostdlib mainexittest.c -o mainexittest
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 00000000080480b8
/tmp/ccXCYx2g.o: In function `main':
mainexittest.c:(.text+0x11): undefined reference to `puts'
mainexittest.c:(.text+0x1d): undefined reference to `exit'
collect2: ld returned 1 exit status
# gcc -nostartfiles mainexittest.c -o mainexittest
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 000000000804821c
这里在指定-nostartfiles后编译链接有个警告提示,这个警告提示可以通过指定-e main(main为入口函数名)或者--entry=main(main为入口函数名,这种方式其实已经没用了。gcc: extraneous argument to '--entry' option)来消除
# gcc -nostartfiles -e main mainexittest.c -o mainexittest
# ./maintest
hello, c!
这里就没有再报段错误(Segmentation fault)了。
我们可以不在程序中编写main入口函数,使用自己编写的一个函数作为入口函数。在下面这个程序中没有入口函数main函数:
#include <stdio.h> #include <stdlib.h> int start() { printf("hello, c!\n"); exit(0); }
我们也可以将它编译出可执行程序。在编译链接的时候通过指定-nostartfiles,在程序链接的时候不使用标准系统启动文件(standard system startup files)
Do not use the standard system startup files when linking. The standard system libraries are used normally, unless -nostdlib, -nolibc, or -nodefaultlibs is used.
同时指定-e start来指定入口函数:
--entry=entry
Specify that the program entry point is entry. The argument is interpreted by the linker; the GNU linker accepts either a symbol name or an address.
编译链接:
# gcc maintest2.c -o maintest2
/usr/lib/gcc/i686-redhat-linux/4.4.7/../../../crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status
因为没有入口函数main函数,这里编译链接的时候失败了。
# gcc -nostartfiles maintest2.c -o maintest2
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 000000000804821c
这里在指定-nostartfiles后编译链接有个警告提示,这个警告提示可以通过指定-e start(start为入口函数名)或者--entry=start(start为入口函数名,这种方式其实已经没用了。gcc: extraneous argument to '--entry' option)来消除
# gcc -nostartfiles -e start maintest2.c -o maintest2
# ./maintest2
hello, c!
下面是在Windows Cygwin下的编译,编译结果表现不一样:
>gcc -nostartfiles maintest2.c -o maintest2
这里编译链接没报什么错。
>.\maintest2
运行没有任何输出。
入口函数main编译后的汇编代码
void main() { }
>gcc -S stacktestmain.c -o stactestmain.S
.file "stacktestmain.c" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp popl %ebp ret .size main, .-main .ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-23)" .section .note.GNU-stack,"",@progbits
下面是在Windows Cygwin下gcc反编译出来的汇编代码:
.file "stacktestmain.c" .def ___main; .scl 2; .type 32; .endef .text .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp movl %esp, %ebp andl $-16, %esp call ___main movl %ebp, %esp popl %ebp ret
其中andl $-16, %esp这条汇编指令是干什么的?
#include <stdio.h> int main() { printf("Hello, World\n"); return 0; }$ gcc -E main.c -o main.i
也可以使用cpp(C PreProcessor预处理器命令)执行预处理:
$ cpp main.c -o main.i
预处理后的结果如下:
# 1 "main.c" # 1 "<built-in>" 1 # 1 "<built-in>" 3 # 330 "<built-in>" 3 # 1 "<command line>" 1 # 1 "<built-in>" 2 # 1 "main.c" 2 ... ... ... ... int printf(const char * restrict, ...) __attribute__((__format__ (__printf__, 1, 2))); ... ... int puts(const char *); ... ... ... ... extern int __vsnprintf_chk (char * restrict, size_t, int, size_t, const char * restrict, va_list); # 499 "/usr/include/stdio.h" 2 3 4 # 2 "main.c" 2 int main() { printf("Hello, World\n"); return 0; }上面预处理后的程序经过了些裁剪,只保留了一些主要的信息。
$ gcc -S main.i -o main.S
编译后的汇编代码如下:
.section __TEXT,__text,regular,pure_instructions .macosx_version_min 10, 12 .globl _main .align 4, 0x90 _main: ## @main .cfi_startproc ## BB#0: pushq %rbp Ltmp0: .cfi_def_cfa_offset 16 Ltmp1: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp2: .cfi_def_cfa_register %rbp subq $16, %rsp leaq L_.str(%rip), %rdi movl $0, -4(%rbp) movb $0, %al callq _printf xorl %ecx, %ecx movl %eax, -8(%rbp) ## 4-byte Spill movl %ecx, %eax addq $16, %rsp popq %rbp retq .cfi_endproc .section __TEXT,__cstring,cstring_literals L_.str: ## @.str .asciz "Hello, World\n" .subsections_via_symbols$ gcc -c main.S -o main.o
汇编之后生成对象文件,到这里就生成了最终代码。也可以通过as汇编器来进行汇编:
$ as main.S -o main.o
$ gcc main.o -o main
链接生成可执行程序,到这里就生成了最终的程序,可以执行。
$ ./main
Hello, World
链接的时候也可以通过ld链接器完成链接
$ ld -o main main.o
ld: warning: -macosx_version_min not specified, assuming 10.10
ld: warning: object file (main.o) was built for newer OSX version (10.12) than being linked (10.10)
Undefined symbols for architecture x86_64:
"_printf", referenced from:
_main in main.o
"start", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for inferred architecture x86_64
这里在链接的时候出错了,没有找到_printf和start符号。程序中调用了printf函数,这里的_printf就是调用printf函数的时候要找到的符号,这个可以理解。至于start符号就不太好理解了,我们在程序中并没有用到呀。先一个个来看。
首先看printf,这个是标准库实现的函数,如libc、glibc。对应的标准库就是libc.dylib,在/usr/lib目录下。
$ ld -o main main.o /usr/lib/libc.dylib
ld: warning: -macosx_version_min not specified, assuming 10.10
ld: warning: object file (main.o) was built for newer OSX version (10.12) than being linked (10.10)
Undefined symbols for architecture x86_64:
"start", referenced from:
implicit entry/start for main executable
ld: symbol(s) not found for inferred architecture x86_64
这样就没有报没有找到_printft符号的错误了。
至于start,在程序中并没有用到,这个其实是编译器提供的,属于运行时库的部分。类似用来引导到main函数执行的,不然程序怎么找到main函数并从main函数开始执行,程序必要要有一个入口函数,这个入口函数默认就是main函数,尽管我们可以指定一个入口函数,这个入口函数并非就一定是main,入口函数名可以是任意的。
start函数可以在crt1.o中找到,这是个对象文件,我们可以像main.o那样直接链接进来:
$ ld -o main main.o /usr/lib/crt1.o /usr/lib/libc.dylib
ld: warning: -macosx_version_min not specified, assuming 10.10
ld: warning: object file (main.o) was built for newer OSX version (10.12) than being linked (10.10)
这里出现了两个警告,但可以运行了
$ ./main
Hello, World
我们可以指定macosx支持的最低版本-macosx_version_min 10.12.0来消除这个警告信息。这个是系统支持的最低版本。
$ ld -macosx_version_min 10.12.0 -o main main.o /usr/lib/crt1.o /usr/lib/libc.dylib
这样就不会出现警告了。至于这个版本为什么是10.12.0,我们可以通过gcc编译的时候指定-v可以看到在链接的时候指定的macosx支持的最低版本,也就是使用的是当前系统的版本。
$ gcc main.c -o main -v
这个其实也不一定就得是10.12.0,得看兼容情况。而且这个10.12.0也不是非的10.12.0,指定成10.12也行。
$ ld -macosx_version_min 10.12 -o main main.o /usr/lib/crt1.o /usr/lib/libc.dylib
根据上面的警告信息,因为没有指定-macosx_version_min,链接的时候它默认的是10.10,我们在指定-macosx_version_min的时候指定为10.10也行,只要兼容就行。
$ ld -macosx_version_min 10.10 -o main main.o /usr/lib/crt1.o /usr/lib/libc.dylib
ld: warning: object file (main.o) was built for newer OSX version (10.12) than being linked (10.10)
也可以以库的形式链接进来,对应的库为libclang_rt.osx.a,这个库的完整路径我这里是/Users/admin/Downloads/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/8.0.0/lib/darwin/libclang_rt.osx.a,如下:
$ ld -arch x86_64 -o main main.o /Users/admin/Downloads/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/8.0.0/lib/darwin/libclang_rt.osx.a /usr/lib/libc.dylib
ld: warning: -macosx_version_min not specified, assuming 10.10
ld: warning: object file (main.o) was built for newer OSX version (10.12) than being linked (10.10)
注意这里需要指定-arch x86_64。
start函数
以下代码来自clang编译器提供的运行时库,libc/loader/linux/x86_64,参考LLVM项目,这里我参考的是llvm-project-llvmorg-12.0.0。
extern "C" void _start() { uintptr_t *frame_ptr = reinterpret_cast<uintptr_t *>(__builtin_frame_address(0)); // This TU is compiled with -fno-omit-frame-pointer. Hence, the previous value // of the base pointer is pushed on to the stack. So, we step over it (the // "+ 1" below) to get to the args. Args *args = reinterpret_cast<Args *>(frame_ptr + 1); // After the argv array, is a 8-byte long NULL value before the array of env // values. The end of the env values is marked by another 8-byte long NULL // value. We step over it (the "+ 1" below) to get to the env values. uint64_t *env_ptr = args->argv + args->argc + 1; uint64_t *env_end_marker = env_ptr; while (*env_end_marker) ++env_end_marker; // After the env array, is the aux-vector. The end of the aux-vector is // denoted by an AT_NULL entry. Elf64_Phdr *programHdrTable = nullptr; uintptr_t programHdrCount; for (AuxEntry *aux_entry = reinterpret_cast<AuxEntry *>(env_end_marker + 1); aux_entry->type != AT_NULL; ++aux_entry) { switch (aux_entry->type) { case AT_PHDR: programHdrTable = reinterpret_cast<Elf64_Phdr *>(aux_entry->value); break; case AT_PHNUM: programHdrCount = aux_entry->value; break; case AT_PAGESZ: app.pageSize = aux_entry->value; break; default: break; // TODO: Read other useful entries from the aux vector. } } for (uintptr_t i = 0; i < programHdrCount; ++i) { Elf64_Phdr *phdr = programHdrTable + i; if (phdr->p_type != PT_TLS) continue; // TODO: p_vaddr value has to be adjusted for static-pie executables. app.tls.address = phdr->p_vaddr; app.tls.size = phdr->p_memsz; app.tls.align = phdr->p_align; } __llvm_libc::initTLS(); __llvm_libc::syscall(SYS_exit, main(args->argc, reinterpret_cast<char **>(args->argv), reinterpret_cast<char **>(env_ptr))); }
这里先主要看最后一条代码:
__llvm_libc::syscall(SYS_exit,
main(args->argc, reinterpret_cast<char **>(args->argv),
reinterpret_cast<char **>(env_ptr)));
入口main函数就是从这里调用的。这条代码的意思是调用main函数,函数返回之后调用syscall系统调用,这个系统就是SYS_exit,类似调用exit函数,main函数返回值作为参数传给syscall。程序终止,终止返回的值就是main函数返回的值。
initTLS函数代码如下:
void initTLS() { if (app.tls.size == 0) return; // We will assume the alignment is always a power of two. uintptr_t tlsSize = (app.tls.size + app.tls.align) & -app.tls.align; // Per the x86_64 TLS ABI, the entry pointed to by the thread pointer is the // address of the TLS block. So, we add more size to accomodate this address // entry. size_t tlsSizeWithAddr = tlsSize + sizeof(uintptr_t); // We cannot call the mmap function here as the functions set errno on // failure. Since errno is implemented via a thread local variable, we cannot // use errno before TLS is setup. long mmapRetVal = __llvm_libc::syscall( mmapSyscallNumber, nullptr, tlsSizeWithAddr, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); // We cannot check the return value with MAP_FAILED as that is the return // of the mmap function and not the mmap syscall. if (mmapRetVal < 0 && static_cast<uintptr_t>(mmapRetVal) > -app.pageSize) __llvm_libc::syscall(SYS_exit, 1); uintptr_t *tlsAddr = reinterpret_cast<uintptr_t *>(mmapRetVal); // x86_64 TLS faces down from the thread pointer with the first entry // pointing to the address of the first real TLS byte. uintptr_t endPtr = reinterpret_cast<uintptr_t>(tlsAddr) + tlsSize; *reinterpret_cast<uintptr_t *>(endPtr) = endPtr; __llvm_libc::memcpy(tlsAddr, reinterpret_cast<const void *>(app.tls.address), app.tls.size); if (__llvm_libc::syscall(SYS_arch_prctl, ARCH_SET_FS, endPtr) == -1) __llvm_libc::syscall(SYS_exit, 1); }
__llvm_libc::syscall的代码可以参考libc/config/linux/x86_64/syscall.h.inc
__attribute__((always_inline)) inline long syscall(long __number, long __arg1) { long retcode; LIBC_INLINE_ASM("syscall" : "=a"(retcode) : "a"(__number), "D"(__arg1) : SYSCALL_CLOBBER_LIST); return retcode; }
_start函数是由loader加载器调用的。
相关推荐
4. **主函数`main()`**:每个C程序都有一个特殊的函数`main()`,它是程序的入口点。所有程序的执行都从`main()`开始。 5. **局部变量与全局变量**:局部变量在函数内部定义,只在该函数内可见;全局变量在整个程序...
在C/C++编程中,入口函数是程序执行的起点,它是程序执行的首个被执行的代码块。最常见的入口函数是`main`函数,它的标准形式如下: ```cpp int main(int argc, char* argv[]) { // 程序代码 return 0; } ``` 在...
在汇编语言中,入口函数通常由一系列特定的指令组成,这些指令包括但不限于: 1. **栈初始化**:设置栈指针寄存器(如x86架构下的ESP/RSP),为程序的局部变量和调用函数预留空间。 2. **数据段初始化**:如果程序...
本文将围绕《C高级语言程序设计:08函数简介》这一主题展开详细论述,深入探讨C语言中的函数概念及其应用。通过理解函数的基本定义、作用、分类以及如何在实际编程中合理运用函数,读者将能够更好地掌握C语言的模块...
本篇文章将深入探讨如何使用DLL函数查看器来查看C语言编写的DLL文件的入口函数。 首先,我们要理解DLL文件的结构。在C语言中,DLL的入口点通常是`DllMain`函数,这是DLL加载和卸载时被系统调用的地方。`DllMain`...
本篇文章将深入探讨如何使易语言编译的DLL不依赖任何插件就能拥有完整的入口函数DllMain。 首先,我们来理解DllMain函数。DllMain是DLL的核心入口点,当DLL被加载到进程地址空间或卸载时,操作系统会调用这个函数。...
每个函数在编译时都有一个唯一的入口地址,函数指针可以存储这个地址。定义一个函数指针变量时,我们需要指定它所指向的函数的参数类型和返回类型。例如: ```c float fun(int a, int b) { return (float)(a + b);...
例如,计算阶乘的递归函数: ```c int factorial(int n) { if (n == 0 || n == 1) return 1; else return n * factorial(n - 1); } ``` 7. **局部变量和全局变量**: - 局部变量仅在函数内部可见,而全局...
"C语言学习笔记劫持函数" C语言学习笔记劫持函数是关于使用C语言实现函数劫持的笔记。函数劫持是指在程序运行时,动态地替换或修改函数的行为,以达到特定的目的。在这个笔记中,我们将使用Detours库来实现函数劫持...
`main()`函数是特殊的,它是程序的入口点,可以调用其他所有函数,但不能被其他函数调用。 4. **函数类型与返回值**:每个函数都有一个特定的返回值类型,定义了函数执行完毕后返回的数据类型。如果函数没有返回值...
1. `abort` 函数:这个函数用于异常终止当前进程,通常在遇到无法恢复的错误时使用。调用 `abort()` 会导致程序立即停止执行并生成核心转储(如果系统设置允许)。 2. `abs` 函数:这是一个用于计算整数绝对值的...
在U-Boot(通用引导加载程序)中,`start_armboot`函数是C语言代码的入口点,负责启动过程中的核心任务。理解`start_armboot`的运作方式,需要先了解一些关键的数据结构,如`gd_t`和`bd_t`,以及初始化函数列表。 1...
5. 递归函数: - 函数可以调用自身,这就是递归。递归在解决某些问题时非常有效,如计算阶乘或遍历树结构。但要注意防止无限递归。 6. 内联函数: - 内联函数是为了提高效率而设计的,它试图避免函数调用的开销。...
- **函数定义**:C程序由一个或多个函数组成,其中`main`函数是程序的入口点。 - **语句**:包括控制语句(如`if`,`for`,`while`)和表达式语句(如函数调用,赋值操作)。 - **注释**:使用`/*`和`*/`或`//`进行...
2. main函数:C语言程序的入口点是main()函数。程序的执行总是从main函数开始,结束于main函数的末尾。 3. 数据类型int:int是C语言中的整数类型,用于存储整数值。在辗转相除法的实现中,变量u和v用int类型表示,...
5. **主函数**:`main()`函数是C程序的入口点,所有程序执行始于`main()`。`int main()`通常是标准的主函数声明,返回一个整数值表示程序的退出状态。 6. **库函数**:C语言标准库提供了一组预定义的函数,如数学...
C语言编写S函数时,通常会遵循MATLAB提供的特定模板,包括几个关键的函数定义,如`simulink.c`或`ssFunction.c`。 1. **初始化函数**:这是S函数的入口点,通常命名为`sfuntmpl_init()`。在这个函数中,我们会设置S...
学生信息管理系统C语言版 学生信息管理系统是使用C语言开发的一款学生信息管理软件,能够实现学生信息的添加、修改...18. 主函数main():该函数是程序的入口点,用于加载学生信息、显示菜单、处理用户输入和退出程序。
- C程序通常由多个函数组成,其中`main`函数是程序的入口点,每个程序至少有一个`main`函数。 - 程序可以包含任意数量的自定义函数,这些函数可以互相调用,但`main`函数不能被其他函数直接调用。 - 程序的执行...
第4题:在一个 C 程序中,main 函数可以在任何地方出现,但它必须是程序的入口点。 第5题:若在 C 语言中未说明函数的类型,则系统默认该函数的数据类型是int类型。 第6题:函数未被调用时,系统将不为形参分配...