main函数真的是C程序的开始吗

C语言中的__attribute__((constructor))和(destructor)属性允许函数在main执行前或后自动调用,类似C++的构造和析构。这些属性用于初始化数据和设置,文章通过示例解释了如何使用它们,并提到了链接过程和函数调用的顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们在学习和编写C程序时,都是从main函数开始,main函数作为入口函数已经深深的印在我们的脑海中,那么main函数真的是C程序的入口函数吗?带着这个问题我们先来看下面一段代码。

1. 实验程序

  • 示例代码
#include <stdlib.h>
#include <stdio.h>

static void __attribute__ ((constructor)) beforeMain(void)
{
    printf("Before main...\n");
}

int main(void)
{
    printf("Main!\n");
    return 0;
}
  • 输出结果
    在这里插入图片描述

  • 为什么最开始执行的不是main函数?怎么和我们刚开始学习C程序时说的不一样呢?从运行结果中,我们可以看出来beforeMain是在进入main函数之前被调用的,这对于C语言的初学者来说似乎有点难以理解。究竟是谁调用的beforeMain呢?怎么还没有进入main就可以有代码被执行呢?

  • 带着以上问题,我们先用-v参数来显示编译过程,其中输出部分如下

/usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccHn29zY.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/5 -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. /tmp/ccMKdwTx.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o

在这里插入图片描述

  • 从输出结果可以看出,在链接生成最后的可执行文件时,有很多的C库二进制文件参与进来。而最终的可执行文件除了我们编写的这个简单的C代码以外,还有大量的C库文件参与了链接,并包含在了最终的可执行文件中。这个链接的过程,是由链接器ld的链接脚本来决定的。如果我们没有指定链接脚本,会默认使用ld的默认脚本。
  • 通过ld -verbose命令来查看
using internal linker script:
==================================================
/* Script for -z combreloc: combine and sort reloc sections */
/* Copyright (C) 2014-2015 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
  • 从上面输出可以看出这里定义了输出的文件格式、目标机器的类型,以及重要的信息和程序的入口ENTRY(_start)。

我们的例子中beforeMain函数使用的gcc扩展属性__attribute__((constructor))就是将函数对应的指令归属于.ctors section部分。

  .ctors          :
  {
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }

2. __attribute__((constructor))属性

The constructor attribute causes the function to be called automatically before execution enters main ().
构造函数属性使函数在执行进入main()之前自动被调用

  • GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。
    __attribute__写法是__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
    __attribute__格式为__attribute__((attribute-list))

  • 就是指在函数上方加上__attribute__((constructor))可以让这个函数在main函数执行前运行

  • 作用:__attribute__((constructor))可以提前初始化一些在main函数中用到的东西,便于我们做一些准备工作。

带有优先级的参数

我们还可以给属性设置优先级,看下面示例代码

#include <stdlib.h>
#include <stdio.h>

static void __attribute__ ((constructor(101))) beforeMain1(void)
{
    printf("Before main...1\n");
}
static void __attribute__ ((constructor(102))) beforeMain2(void)
{
    printf("Before main...2\n");
}
static void __attribute__ ((constructor(103))) beforeMain3(void)
{
    printf("Before main...3\n");
}

int main(void)
{
    printf("Main!\n");
    return 0;
}

在这里插入图片描述

3. __attribute__((destructor))属性

  • 查阅了GNU的文档你还会发现有提及这么一个写法__attribute__((destructor)),文档中关于这两个用法的说明如下:

The constructor attribute causes the function to be called automatically before execution enters main (). Similarly, the destructor attribute causes the function to be called automatically after main () completes or exit () is called. Functions with these attributes are useful for initializing data that is used implicitly during the execution of the program.

同理, destructor让系统在main()函数退出或者调用了exit()之后,调用我们的函数。

#include <stdlib.h>
#include <stdio.h>

static void __attribute__ ((constructor)) beforeMain(void)
{
    printf("Before main...\n");
}

static void __attribute__ ((destructor)) afterMain(void)
{
    printf("After main...\n");
}

int main(void)
{
    printf("Main!\n");
    return 0;
}

在这里插入图片描述

总结

C程序中__attribute__ ((constructor))__attribute__ ((destructor))类似于C++类中构造函数和析构函数。在main函数之前,执行一个函数,便于我们做一些准备工作;在main()函数退出或者调用了exit()之后调用。多个函数时,GCC为我们提供了一个参数叫优先级,constructor按从小到大,destructor函数相反 void __attribute__((constructor(5)) initFunction1(void); void __attribute__((constructor(10)) initFunction2(void);
了一个参数叫优先级,constructor按从小到大,destructor函数相反 void __attribute__((constructor(5)) initFunction1(void); void __attribute__((constructor(10)) initFunction2(void);

第一时间获取干货文章,领取海量Linux学习资料,请私信联系或公人从号:Linux兵工厂

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux兵工厂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值