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

时间:2011-08-13 14:11:48

标签: c++ c compilation main

我在采访中不断提出这个问题:

在不使用main()功能的情况下编写程序?

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

所以问题是:

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

22 个答案:

答案 0 :(得分:27)

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

根据:

C ++ 03标准 3.6.1主要功能

  

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


什么是freestanding Environment&什么是Hosted Environment
C ++标准中定义了两种符合实现的方法; hostedfreestanding

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

使用操作系统功能的程序通常位于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>

答案 1 :(得分:17)

在标准C ++中,需要main函数,因此对标准C ++没有意义。

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

除此之外,您可以创建自己的小型运行时库。曾经是一项普通运动。

最后,根据标准的ODR规则main未被“使用”,您可以获得聪明和反驳,因此任何计划都有资格。呸!虽然除非面试官有不同寻常的幽默感(并且他们不会问问题是否有),但他们认为这不是一个好的答案。

答案 2 :(得分:15)

没有可见主要功能的示例程序。

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

答案 3 :(得分:11)

main表示一个入口点,代码将从该点开始执行。虽然main不是第一个运行的函数。还有一些代码在main之前运行,并准备环境以使代码运行。然后,此代码调用main。您可以通过重新编译启动文件main的代码并更改crt0.c函数的名称来更改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但它会带来痛苦,因为它会违反标准。

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

答案 4 :(得分:8)

他们可能会参考为独立实施而编写的程序。 C ++标准定义了两种实现。一个是托管的实现。为这些实现编写的程序需要具有main功能。但是,如果独立实现不需要main功能,则不需要{{1}}功能。这对于不在操作系统下运行的操作系统内核或嵌入式系统程序很有用。

答案 5 :(得分:5)

是的,可以用main编译但是你不能通过链接阶段。

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

答案 6 :(得分:5)

$ 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 :(得分:3)

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

struct MainSub
{
   MainSub()
   {
      // do some stuff
   }
};

MainSub mainSub;

int main(int argc, char *argv[]) { return 0; }

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

答案 8 :(得分:2)

我认为宏引用是重命名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 ");
}

答案 9 :(得分:2)

只要您使用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 :(得分:1)

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

docker-compose logs <node_service_name_in_docker-compose.yml>

# add "-f" if you want to see realtime logs

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

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

答案 11 :(得分:1)

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

int main[] {};

甚至只是

int main;

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

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

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

答案 12 :(得分:1)

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

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

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

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

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

答案 13 :(得分:1)

忽略特定的语言标准,大多数链接加载器提供了一些方法来声明在加载二进制文件时必须执行的函数名(入口点)。

对于旧式c语言,默认类似于'start'或'_start',在所谓的crt(c runtime?)中定义,它执行c标准函数所需的几个家庭工作,例如准备内存堆,初始化静态变量区域,将命令行解析为argc / argv等

如果你注意不要使用需要那些家庭用品的标准函数(例如malloc(),free(),printf(),任何类定义都有自定义构造函数,那么你可以覆盖入口点函数,... 。) 如果您使用o / s提供的函数,而不是标准的c运行时,则相当严格但不是不可能。

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

答案 14 :(得分:0)

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

答案 15 :(得分:0)

这取决于他们的意思。

他们的意思是:

  

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

然后一般来说没有。
但有办法欺骗。

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

或者他们的意思是:

  

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

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

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

答案 16 :(得分:0)

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

class MyClass
{
   myClass()
   {
       cout << "printing my name..." <<endl;
   }
};

MyClass gObj; // this will trigger the constructor.

int main()
{
   // nothing here...
}

答案 17 :(得分:0)

1)使用定义主

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

输出:

计算器

2)使用令牌粘贴运算符 上面的解决方案中有“main”字样。如果我们不允许写main,我们可以使用令牌粘贴操作符(详情请参阅)

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

答案 18 :(得分: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“);
}

'##'运算符称为标记粘贴或标记合并运算符。也就是说,我们可以合并两个或多个字符。

在该计划的第二行 -

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

这里的预处理器是什么?宏解码(s,t,u,m,p,e,d)正在扩展为“msut”(##运算符将m,s,u&amp; t合并为msut)。逻辑是当你传递(s,t,u,m,p,e,d)作为参数时,它合并了第4个,第1个,第3个和第4个。第二个字符(代币)

现在看一下该程序的第三行 -

定义开始解码(a,n,i,m,a,r,e)

这里预处理器用扩展解码(a,n,i,m,a,r,e)替换宏“begin”。根据前一行中的宏定义,必须扩展参数,以便第4,第1,第3和第4版。必须合并第二个字符。在论证中(a,n,i,m,a,r,e)第4,第1,第3&amp;第二个字符是'm','a','i'和amp; “N”。

所以它会在为编译器传递程序之前由预处理器替换main()。就是这样......

答案 19 :(得分:0)

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

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

 int main(){}

答案 20 :(得分:0)

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

答案 21 :(得分:-4)

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