【问题标题】:Is it possible to write a program without using main() function?是否可以在不使用 main() 函数的情况下编写程序?
【发布时间】:2011-10-26 10:15:25
【问题描述】:

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

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

我的一个朋友给我看了一些使用宏的代码,但我看不懂。

所以问题是:

真的可以不用main()来编写和编译程序吗?

【问题讨论】:

  • 似乎是一个非常愚蠢的面试问题......也许你忘记了一些细节?
  • 你不喜欢这些实用的面试问题吗? /讽刺
  • 这如何准确地表明候选人是否能够解决问题或适应?编写和维护可读程序已经够难了,为什么要问这种可憎的事情呢?除非这家公司叫 IOCCC,否则就是这样。
  • 这取决于他们的意思。他们的意思是“没有 main() 函数”吗?还是他们的意思是“运行代码而不从 main(甚至间接)运行它”?
  • “不断被问到”?什么,一些标题为“TEH BESTEST C+ 面试问题”的列表刚刚发布在互联网上吗?你现在在哪里?你在采访谁?因为我想往另一个方向跑。我的建议是,如果有人问你这个问题,你就消除他们。顺便说一句,答案是“是”,您可以“编写和编译”这样的程序,只是无法成功链接它(至少在标准、托管、兼容的系统上)。我可能会很生气,但如果我被问到这个问题,那将是我的聪明回答。

标签: c++ c compilation main


【解决方案1】:

不,除非您在freestanding environment(嵌入式环境操作系统内核等)中编写程序,起点不必是main()。根据 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>

【讨论】:

  • 你确定吗?我的意思是每个编写 OS 内核或嵌入式程序的人都必须不使用 main 函数,我希望这样的事情不会导致未定义的行为。
  • 在我看来,你首先说“不,你不能”这很奇怪。但是你引用了规范,它说有时你可能会省略定义它。
  • @Johannes Schaub - litb:啊,我将添加独立环境的例外情况。
  • @Voo:没有像嵌入式程序或操作系统内核这样的独立环境可以省略main,请查看标准中的引用。
【解决方案2】:

在标准 C++ 中,main 函数是必需的,所以这个问题对标准 C++ 没有意义。

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

除此之外,您还可以制作自己的小型运行时库。曾经这是一项常见的运动。

最后,根据标准的 ODR 规则 main 没有“使用”,您可以聪明地反驳说,因此任何程序都符合条件。呸!尽管除非面试官有非同寻常的幽默感(如果他们有,他们也不会问这个问题),他们不会认为这是一个好的答案。

【讨论】:

  • 哦。我本来打算让这个答案为 B,所有 2 C 都有错误。(好吧,刚刚看了《星球大战》电影)。它通过例如补充和纠正。 Als' answerJohannes Schaub's answer。现在,以防后来的因果读者看不到它:独立环境,例如例如DSP的编译器,允许main以外的其他启动功能。
  • @anonymous downvoter:请解释您拒绝投票的原因,以便其他人可以从您的见解中受益。
  • 我不是匿名投反对票的人——但我怀疑这是一个反对这个被接受的答案的投票,当它不准确时。这是一个非常糟糕的面试问题 - 但这个答案具有误导性,因为它仅适用于(公认更常见的)托管环境。
【解决方案3】:

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

/* 
    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/

【讨论】:

  • 实际上,this#define begin() main() 一样好,您可以添加许多鲜为人知/流行的 C/C++ 结构并将其屏蔽,但必须为托管环境定义主要结构实施,在此处添加此评论,因为没有此答案可能会误导新手。
  • 这段代码不是没有main。 gcc -E file.c 会给你实际正在编译的代码。
【解决方案4】:

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("Hello\n");
  return 0;
}

编译代码:

gcc -o no_main no_main.c -nostartfiles

-nostartfiles 将不包含默认启动文件。你用_start 指向主入口文件。

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

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      是的

      $ 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, "hi\n", 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。

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

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

          这里会发生的是MainSub的构造函数中的东西会在不可用的main被执行之前执行,你可以把程序的逻辑放在那里。这当然需要 C++,而不是 C(从问题中也不清楚)。

          【讨论】:

          • 为什么MainSub::MainSub()会被执行?你不在任何地方构造一个 MainSub 对象?
          • @Dan,是的,我愿意。就在结构定义的正下方,int main 的正上方。
          • 哦,对了,我以为这只是一个声明。如果它是一个原语,即int,它的值将是未定义的,不是吗?
          • @Dan,如果是int,那就是initialized to 0
          • 您一直在学习有关 C++ 的新知识。我从来不知道……尽管我确实必须对其进行测试;)+1 教我一些新东西。
          【解决方案9】:

          只要您使用 g++,您就可以使用链接器选项 -e 更改您的入口点,因此以下代码和编译命令可以让您创建一个没有 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 类的构造函数相同。但是,我也有几个原因可以告诉我我的猜测可能不正确。

          【讨论】:

            【解决方案10】:

            我认为宏引用是重命名主函数,以下不是我的代码,并演示了这一点。虽然编译器仍然看到一个 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 ");
            }
            

            【讨论】:

              【解决方案11】:

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

              对于老式的 c 语言,默认值是类似于 'start' 或 '_start' 的东西,在所谓的 crt(c 运行时?)中定义,它执行 c 标准函数所需的几项日常工作,例如准备内存堆,初始化静态变量区,解析命令行为argc/argv等

              如果你足够小心不要使用需要那些日常用品的标准函数(例如 malloc()、free()、printf(),任何类定义都有自定义构造函数,那么你可能会覆盖入口点函数,.. .) 如果您使用由 o/s 提供的函数,而不是标准 c 运行时提供的函数,则相当有限,但并非不可能。

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

              【讨论】:

                【解决方案12】:

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

                执行此操作的代码在构建时由链接器链接到您的代码。在 GCC 中,它通常位于 crt0.s 中,使用商业编译器不太可能提供给您。

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

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

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

                【讨论】:

                  【解决方案13】:

                  编写一个类并在该类的构造函数中打印您的名字并声明该类的 GLOBAL OBJECT。所以类的构造函数在 main 之前执行。因此,您可以将主目录留空,但仍然可以打印您的姓名。

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

                  【讨论】:

                    【解决方案14】:

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

                    int main[] {};
                    

                    甚至只是

                    int main;
                    

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

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

                    exit(0) 是为了防止数组被“调用”。很好玩:-)

                    【讨论】:

                      【解决方案15】:

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

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

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

                      gcc -nostartfiles test.c
                      ./a.out
                      

                      【讨论】:

                        【解决方案16】:

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

                        【讨论】:

                          【解决方案17】:

                          这取决于他们的意思。

                          他们的意思是:

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

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

                          • 您可以使用预处理器将 main() 隐藏起来。
                          • 大多数编译器都允许您指定代码的入口点。
                            默认是 main(int,char*[])

                          或者他们的意思是:

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

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

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

                          【讨论】:

                            【解决方案18】:

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

                            【讨论】:

                              【解决方案19】:

                              1) 使用定义 main 的宏

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

                              输出:

                              堆栈溢出

                              2) 使用标记粘贴操作符 上述解决方案中包含“main”一词。如果我们连main都不能写,我们可以使用token-pasting操作符(详见这里)

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

                              【讨论】:

                                【解决方案20】:

                                是的,可以在没有 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“);
                                }
                                

                                ‘##’操作符称为标记粘贴操作符或标记合并操作符。那就是我们可以用它合并两个或多个字符。

                                在程序的第二行-

                                定义解码(s,t,u,m,p,e,d) m##s##u##t

                                预处理器在这里做什么。宏 decode(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”替换为展开式 decode(a,n,i,m,a,r,e)。根据上一行中的宏定义,必须扩展参数,以便必须合并第 4、第 1、第 3 和第 2 个字符。在参数 (a,n,i,m,a,r,e) 中,第 4、1、3 和第 2 个字符分别为“m”、“a”、“i”和“n”。

                                所以它会在程序被传递给编译器之前由预处理器用 main() 代替 begin。就是这样……

                                【讨论】:

                                  【解决方案21】:

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

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

                                  【讨论】:

                                    【解决方案22】:

                                    根据标准,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 ");
                                    }
                                    

                                    这里,main 是由宏技巧编写的。可能一下子不清楚,但最终会导致主要。如果这是您问题的有效答案,则可以像这样轻松完成。

                                    # include <stdio.h>
                                    # define m main
                                    
                                    int m()
                                    {
                                        printf("Hell0");
                                    }
                                    

                                    【讨论】:

                                    • 有人能告诉我投反对票的原因,以便我以后能照顾好吗?
                                    • 我没有投反对票,但原因很清楚,您的回答与this 回答和我对此发表的评论有何不同?时间戳告诉我那是您发布此答案之前的 5 小时。
                                    • @Als :谢谢你让我明白了原因。顺便说一句,这不是我的答案。我只是想判断这个技巧(我想引用那个)是否有效,那么任何更简单的技巧都应该像我的例子一样工作。而且我忘了阅读答案下方发布的 cmets。
                                    • 您需要阅读 SO 指南。简而言之,回答 Q 的帖子应作为答案发布。 cmets、更正、建议、对答案的批评应作为 cmets 而不是答案发布。
                                    猜你喜欢
                                    • 1970-01-01
                                    • 2018-02-23
                                    • 1970-01-01
                                    • 2021-01-08
                                    • 2021-01-02
                                    • 2012-06-27
                                    • 2011-03-18
                                    • 2019-12-08
                                    • 2010-11-04
                                    相关资源
                                    最近更新 更多