是否可以在不使用main()函数的情况下编写程序

Is it possible to write a program without using main() function?

本文关键字:函数 情况下 程序 main 是否      更新时间:2023-10-16

我在面试中不断被问到这个问题:

不使用main()函数编写程序?

我的一个朋友向我展示了一些使用宏的代码,但我无法理解它。

所以问题是:

真的可以在没有main()的情况下编写和编译程序吗?

不,除非您在起点不需要main()freestanding environment(嵌入式环境操作系统内核等(中编写程序,否则您不能。根据C++标准,main()hosted environment中任何程序的起点。

根据:

C++03 标准 3.6.1 主要功能

1 程序应包含一个名为main的全局函数,这是程序的指定启动。是否需要独立环境中的程序来定义主函数,这是实现定义的。[ 注意:在独立环境中,启动和 终止是实现定义的;启动包含对具有静态存储持续时间的命名空间范围的对象的构造函数的执行;终止包含对具有静态存储持续时间的对象执行析构函数。


什么是freestanding Environment和什么是Hosted Environment
C++标准中定义了两种符合要求的实现; hostedfreestanding.

freestanding实现是为在没有操作系统优势的情况下执行的程序而设计的实现。
例如:操作系统内核或嵌入式环境将是一个独立的环境。

使用操作系统功能的程序通常会处于hosted implementation中。

从 C++03 标准第 1.4/7 节

独立实现是指可以在没有操作系统优势的情况下执行的实现,并且具有一组实现定义的库,其中包括某些语言支持库。

进一步
部分: 17.4.1.3.2 独立实现引用:

独立实现具有一组实现定义的标头。 此集合应至少包括以下标头,如表所示:

18.1 Types <cstddef>   
18.2 Implementation properties <limits>   
18.3 Start and termination <cstdlib> 
18.4 Dynamic memory management <new> 
18.5 Type identification <typeinfo> 
18.6 Exception handling <exception> 
18.7 Other runtime support <cstdarg>

在标准C++中需要一个main函数,因此这个问题对于标准C++没有意义。

例如,在

标准C++之外,您可以编写Windows特定的程序并使用Microsoft的自定义启动函数之一(wMain,winMain,wWinmain(。在Windows中,您还可以将程序编写为DLL并使用rundll32运行它。

除此之外,您还可以创建自己的小运行时库。曾几何时,这是一项常见的运动。

最后,你可以聪明地反驳说,根据标准的ODR规则main没有被"使用",所以任何程序都有资格。呸!尽管除非面试官有不同寻常的幽默感(如果他们有,他们就不会问这个问题(,否则他们不会认为这是一个好的答案。

没有可见主函数的示例程序。

/* 
    7050925.c 
    $ gcc -o 7050925 7050925.c
*/
#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("How mainless!n");
}

寄件人: http://learnhacking.in/c-program-without-main-function/

main表示入口点,代码将从该点开始执行。 尽管main不是第一个运行的函数。还有一些代码在main之前运行,并准备环境以使代码运行。然后,此代码调用 main 。您可以通过重新编译启动文件的代码crt0.c并更改main函数的名称来更改main函数的名称。或者,您可以执行以下操作:

#include <stdio.h>
extern void _exit (register int code);
_start()
{
  int retval;
  retval = my_main ();
  _exit(retval);
}
int my_main(void)
{
  printf("Hellon");
  return 0;
}

编译代码:

gcc -o no_main no_main.c -nostartfiles

-nostartfiles将不包括默认启动文件。您指向带有_start的主条目文件。

main只不过是用户代码的预定义入口点。因此,您可以将其命名为任何名称,但归根结底,您确实需要一个入口点。在C/C++和其他语言中,选择名称main如果您制作另一种语言或破解这些语言编译器的源代码,则可以将main的名称更改为pain但这会带来痛苦,因为它会违反标准。

但是,操作入口函数名称对于内核代码、内核中运行的第一个函数或为嵌入式系统编写的代码很有用。

它们可能是指为独立实现编写的程序。C++标准定义了两种实现。一个是托管实现。为这些实现编写的程序需要具有main功能。但除此之外,如果独立实现不需要main函数,则不需要该函数。这对于不在操作系统下运行的操作系统内核或嵌入式系统程序非常有用。

$ cat > hwa.S
write = 0x04
exit  = 0xfc
.text
_start:
        movl    $1, %ebx
        lea     str, %ecx
        movl    $len, %edx
        movl    $write, %eax
        int     $0x80
        xorl    %ebx, %ebx
        movl    $exit, %eax
        int     $0x80
.data
str:    .ascii "Hello, world!n"
len = . -str
.globl  _start
$ as -o hwa.o hwa.S
$ ld hwa.o
$ ./a.out
Hello, world!

真正运行可执行文件的内核对内部符号一无所知,它只是传输到可执行映像标头中以二进制指定的入口点。

你需要一个主程序的原因是,通常你的"主程序"实际上只是另一个模块。入口点位于库提供的启动代码中,该启动代码以 C 和汇编的某种组合编写,并且该库代码恰好调用main因此通常需要提供一个。但是直接运行链接器,你不会。

要包含 C 模块1...

Mac:~/so$ cat > nomain.S
.text
.globl start
start:
        call   _notmain
Mac:~/so$ as -o nomain.o nomain.S
Mac:~/so$ cat > notmain.c
#include <unistd.h>
void notmain(void) {
  write(1, "hin", 3);
  _exit(0);
}
Mac:~/so$ cc -c notmain.c
Mac:~/so$ ld -w nomain.o notmain.o -lc
Mac:~/so$ ./a.out
hi


1.我在这里也切换到x86-64。

是的,可以在没有 main 的情况下进行编译,但您无法通过链接阶段。

 g++ -c noMain.cpp -o noMain.o

"不使用main "也可能意味着main内不允许任何逻辑,但main本身存在。我可以想象这个问题已经清除了,但由于这里没有清除,这是另一个可能的答案:

struct MainSub
{
   MainSub()
   {
      // do some stuff
   }
};
MainSub mainSub;
int main(int argc, char *argv[]) { return 0; }

这里将发生的情况是,MainSub 的构造函数中的内容将在执行不可用的main之前执行,您可以将程序的逻辑放在那里。这当然需要C++,而不是 C(从问题中也不清楚(。

只要你使用的是 g++,你就可以使用链接器选项-e更改入口点,因此以下代码和 compile 命令可能允许您创建一个没有 main() 函数的程序:

#import <iostream>
class NoMain
{
public:
    NoMain()
    {
        std::cout << "Hello World!" << std::endl;
        exit(0);
    }
} mainClass;

我给出文件名作为noname.cpp,编译选项是:

g++ nomain.cpp -Wl,-e,_mainClass -v

说实话,我不完全理解为什么代码可以正常工作。我怀疑全局变量 mainClass 的地址与NoMain类的构造函数相同。但是,我也有几个原因可以说我的猜测可能不正确。

我认为宏参考是重命名主函数,下面不是我的代码,并演示了这一点。编译器仍然看到一个 main 函数,但从技术上讲,从源的角度来看没有 main。我在这里得到了它 http://www.exforsys.com/forum/c-and-c/96849-without-main-function-how-post412181.html#post412181

#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(" hello ");
}

不考虑特定的语言标准,大多数链接加载器提供了一些方法来声明函数名称(入口点(,该函数名称必须在二进制文件加载到内存中时执行。

对于老式的 c 语言,默认值类似于"start"或"_start",在所谓的 crt(c 运行时?(中定义,它执行 c 标准功能所需的几个内务工作,例如准备内存堆、初始化静态变量区域、将命令行解析为 argc/argv 等。

如果您足够小心,不要使用需要那些家喻户晓的标准函数(例如 malloc((,free((,printf((,任何类定义都有自定义构造函数,...相当严格,但如果您使用由 o/s 而不是标准 c 运行时提供的函数,则并非不可能。

例如,您可以在描述符 1 上使用 write(( 函数创建一个简单的 helloworld。

当 C 或 C++ 代码运行时,它会在已知的起始地址执行,这里的代码初始化运行时环境,初始化堆栈指针,执行数据初始化,调用静态构造函数,然后跳转到 main((。

执行此操作的代码在生成时由链接器与代码链接。 在 GCC 中,它通常使用 crt0.s,对于商业编译器,您不太可能使用此代码。

最后,它必须从某个地方开始,main()只是该位置的象征性名称。 它由语言标准指定,以便开发人员知道该怎么称呼它,否则代码将无法从一个工具链移植到另一个工具链。

如果您正在为没有操作系统或至少没有进程加载程序意义上的操作系统的"裸机"系统编写代码(嵌入式系统通常包括一个在 main(( 之后启动的 RTOS 内核(,那么理论上您可以随心所欲地调用 C 代码入口点,因为您通常可以完全控制运行时启动代码。 但这样做是愚蠢的,而且有些反常。

一些RTOS环境(如VxWorks(和大多数应用程序框架通常在其自己的库代码中包含main((或其等效项,以便它在用户应用程序代码之前运行。 例如,VxWorks 应用程序从 usrAppInit(( 开始,Win32 应用程序从 WinMain(( 开始。

编写一个类并在该类的构造函数中打印您的名字,并声明该类的全局对象。因此,类的构造函数在 main 之前执行。因此,您可以将主要内容留空,但仍打印您的名字。

class MyClass
{
   myClass()
   {
       cout << "printing my name..." <<endl;
   }
};
MyClass gObj; // this will trigger the constructor.
int main()
{
   // nothing here...
}

我意识到这是一个老问题,但我刚刚发现这一点并不得不分享。它可能不会适用于所有链接器,但至少可以通过这样做来欺骗ld(我正在运行版本 2.24.51.20140918(认为有一个主函数

int main[] {};

甚至只是

int main;

然后,您可以应用上述技巧之一让程序执行一些代码,例如通过使用构造函数:

struct Main
{
    Main()
    {
        cout << "Hello World!n";
        exit(0);
    }
} main_;

exit(0)是防止数组被"调用"。很好玩 :-(

是的,您可以通过将 C 语言的入口点从 main(( 更改为 _start 来做到这一点这是代码:

#include<stdio.h>
#include<stdlib.h>
int sid()
{
printf("Hallo Worldn");
exit(0);
}

然后使用 gcc 编译器运行代码。假设您已经使用 test.c 的名称保存了文件。

gcc -nostartfiles test.c
./a.out

也许可以编译一个 .data 部分并用代码填充它?

这取决于它们的含义。

他们的意思是:

编写一个没有 main(( 函数的程序。

那么一般来说没有。
但是有作弊的方法。

  • 您可以使用预处理器将 main(( 隐藏在众目睽睽之下。
  • 大多数编译器允许您指定代码的入口点。
    默认情况下它是main(int,char*[](

或者他们的意思是:

编写一个不使用 main 运行代码的程序(以运行代码(。

这是一个相对简单的技巧。全局命名空间中的所有对象在输入 main(( 之前运行其构造函数,在 main(( 退出后销毁。因此,您需要做的就是使用运行所需代码的构造函数定义一个类,然后在全局命名空间中创建一个对象。

注意:编译器可以针对延迟加载优化这些对象(但通常不会(,但为了安全起见,只需将全局与 main 函数(可以为空(放在同一个文件中即可。

函数 main 只是程序开始执行的地址的默认标签。所以从技术上讲是可能的,但你必须设置将在你的环境中开始执行的函数的名称。

1( 使用定义 main 的宏

#include<stdio.h>
#define fun main
int fun(void)
{
printf("stackoverfow");
return 0;
}

输出:

堆栈溢出

2( 使用令牌粘贴运算符上面的解决方案中有"主要"一词。如果我们甚至不允许编写 main,我们使用令牌粘贴运算符(有关详细信息,请参阅此(

#include<stdio.h>
#define fun m##a##i##n
int fun()
{
printf("stackoverflow");
return 0;
}

是的,可以在没有main((的情况下编写程序。

但它间接使用 main((。

以下程序将帮助您理解..

#include<stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,r,e)
int begin()
{
printf(” you are inside main() which is hidden“);
}

"##"运算符称为令牌粘贴或令牌合并运算符。也就是说,我们可以将两个或多个字符与它合并。

在程序的第二行-

define decode(s,t,u,m,p,e,d( m##s##u##t

预处理器在这里做什么。宏解码(s,t,u,m,p,e,d(被扩展为"msut"(##运算符将m,s,u&t合并到msut中(。逻辑是当你传递(s,t,u,m,p,e,d(作为参数时,它会合并第4,1,3和第2个字符(标记(

现在看看程序的第三行——

定义开始解码(A,N,I,M,A,R,E(

在这里,预处理器将宏"begin"替换为扩展解码(a,n,i,m,a,r,e(。根据上一行中的宏定义,必须扩展参数,以便必须合并第 4、1、3 和 2 个字符。在参数(a,n,i,m,a,r,e(中,第4,1st,3rd和第2个字符是"m","a","i"和"n"。

因此,在程序传递给编译器之前,它将由预处理器替换 main(( 的 begin。就是这样。。。

通过使用C++构造函数,您可以编写一个没有main函数的C++程序。例如,假设我们可以打印一个hello world,而无需在main函数中编写任何内容,如下所示:

class printMe{
   private:
   //
   public:
   printMe(){
       cout<<"Hello Wold! "<<endl;
  }
       protected:
       //
 }obj;
 int main(){}

根据标准,main(( 是必需的,并且是托管环境的起点。这就是为什么你必须使用技巧来隐藏明显的主图,就像上面发布的技巧一样。

#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(" hello ");
}

在这里,主要由宏技巧编写。它可能一下子不清楚,但它最终会导致主要。如果这是您问题的有效答案,那么这可以很容易地完成,就像这样。

# include <stdio.h>
# define m main
int m()
{
    printf("Hell0");
}
相关文章: