你真的需要 C++ 中的 main() 吗?

发布于 2024-10-03 18:54:45 字数 110 浏览 1 评论 0原文

据我所知,当您创建全局对象时,您可以启动构造函数中的所有操作。那么你真的需要 C++ 中的 main() 函数还是它只是遗留的?

我可以理解这样做可能被认为是不好的做法。我只是出于好奇才问。

From what I can tell you can kick off all the action in a constructor when you create a global object. So do you really need a main() function in C++ or is it just legacy?

I can understand that it could be considered bad practice to do so. I'm just asking out of curiosity.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

灯角 2024-10-10 18:54:45

如果您想在托管 C++ 实现上运行程序,则需要一个 main 函数。这就是事物的定义方式。当然,如果您愿意,也可以将其留空。在技​​术方面,链接器想要解析运行时库中使用的 main 符号(它不知道您省略它的特殊意图 - 它只是仍然发出对它的调用) 。如果标准指定 main 是可选的,那么实现当然可以提出解决方案,但这需要在平行宇宙中发生。

如果您选择“执行在我的全局对象的构造函数中开始”,请注意您会给自己带来许多与不同翻译单元中定义的命名空间范围对象的构造顺序相关的问题(那么是什么 入口点?答案是:你将有多个入口点,并且首先执行哪个入口点是未指定的!)。在 C++03 中,您甚至不能保证 cout 被正确构造(在 C++0x 中,您可以保证在任何代码尝试使用它之前,只要有前面包含 )。

如果您正确地开始在 ::main 中执行操作,那么您就不会遇到这些问题,也不需要解决它们(这可能非常棘手)。


正如评论中提到的,有一些系统通过让用户告诉在 main 中实例化的类的名称来向用户隐藏 main代码>.其工作原理类似于以下示例

class MyApp {
public:
  MyApp(std::vector<std::string> const& argv);

  int run() {
      /* code comes here */
      return 0;
  };
};

IMPLEMENT_APP(MyApp);

对于该系统的用户来说,完全隐藏了 main 函数,但该宏实际上会定义这样一个主函数,如下所示

#define IMPLEMENT_APP(AppClass) \
  int main(int argc, char **argv) { \
    AppClass m(std::vector<std::string>(argv, argv + argc)); \
    return m.run(); \
  }

这没有问题上述未指定的构建顺序。它们的好处是它们可以与不同形式的更高级别入口点一起工作。例如,Windows GUI 程序在 WinMain 函数中启动 - IMPLMENT_APP 然后可以在该平台上定义这样的函数。

If you want to run your program on a hosted C++ implementation, you need a main function. That's just how things are defined. You can leave it empty if you want of course. On the technical side of things, the linker wants to resolve the main symbol that's used in the runtime library (which has no clue of your special intentions to omit it - it just still emits a call to it). If the Standard specified that main is optional, then of course implementations could come up with solutions, but that would need to happen in a parallel universe.

If you go with the "Execution starts in the constructor of my global object", beware that you set yourself up to many problems related to the order of constructions of namespace scope objects defined in different translation units (So what is the entry point? The answer is: You will have multiple entry points, and what entry point is executed first is unspecified!). In C++03 you aren't even guaranteed that cout is properly constructed (in C++0x you have a guarantee that it is, before any code tries to use it, as long as there is a preceeding include of <iostream>).

You don't have those problems and don't need to work around them (wich can be very tricky) if you properly start executing things in ::main.


As mentioned in the comments, there are however several systems that hide main from the user by having him tell the name of a class which is instantiated within main. This works similar to the following example

class MyApp {
public:
  MyApp(std::vector<std::string> const& argv);

  int run() {
      /* code comes here */
      return 0;
  };
};

IMPLEMENT_APP(MyApp);

To the user of this system, it's completely hidden that there is a main function, but that macro would actually define such a main function as follows

#define IMPLEMENT_APP(AppClass) \
  int main(int argc, char **argv) { \
    AppClass m(std::vector<std::string>(argv, argv + argc)); \
    return m.run(); \
  }

This doesn't have the problem of unspecified order of construction mentioned above. The benefit of them is that they work with different forms of higher level entry points. For example, Windows GUI programs start up in a WinMain function - IMPLEMENT_APP could then define such a function instead on that platform.

沩ん囻菔务 2024-10-10 18:54:45

是的!你可以去掉 main.

免责声明:您问的是是否可能,而不是是否应该这样做。这是一个完全不受支持的坏主意。我自己也这样做过,原因我不会详细说明,但我不推荐这样做。我的目的不是删除 main,但它也可以做到这一点。

基本步骤如下:

  1. 在编译器的 CRT 源目录中查找 crt0.c
  2. crt0.c 添加到您的项目(副本,而不是原始项目)。
  3. 查找并删除 crt0.c 中对 main 的调用。

编译和链接它可能很困难;难度取决于哪个编译器和哪个编译器版本。

已添加

我刚刚使用 Visual Studio 2008 完成了此操作,因此以下是使其与该编译器一起使用所需执行的具体步骤。

  1. 创建一个新的 C++ Win32 控制台应用程序(单击“下一步”并选中“空项目”)。
  2. 添加新项目..C++ 文件,但将其命名为 crt0.c(而不是 .cpp)。
  3. 复制 C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\crt0.c 的内容并粘贴到 crt0.c 中。
  4. 找到 mainret = _tmain(__argc, _targv, _tenviron); 并将其注释掉。
  5. 右键单击 crt0.c 并选择“属性”。
  6. 设置C/C++ ->一般->其他包含目录 = “C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src”
  7. 设置C/C++ ->预处理器->预处理器定义 = _CRTBLD
  8. 单击“确定”。
  9. 右键单击项目名称并选择属性。
  10. 设置C/C++ ->代码生成->运行时库 = 多线程调试 (/MTd) (*)。
  11. 单击“确定”。
  12. 添加新项目..C++ 文件,将其命名为任意名称(本例中为app.cpp)。
  13. 将以下代码粘贴到 app.cpp 中并运行它。

(*) 您不能使用运行时 DLL,必须静态链接到运行时库。

#include <iostream>

class App
{
    public: App()
    {
        std::cout << "Hello, World! I have no main!" << std::endl;
    }
};

static App theApp;

添加

我删除了多余的退出调用和有关生命周期的简介,因为我认为我们都有能力理解删除 main 的后果。

Ultra Necro

我刚刚看到这个答案并阅读了它和下面约翰·迪布林的反对意见。显然,我没有解释上述过程的作用以及为什么它确实从程序中完全删除了 main 。

约翰断言,CRT 中“总有一个主线”。这些话严格来说并不正确,但该声明的精神是正确的。 Main不是CRT提供的函数,必须自己添加。对该函数的调用位于 CRT 提供的入口点函数中。

每个 C/C++ 程序的入口点都是名为“crt0”的模块中的一个函数。我不确定这是否是一种约定或语言规范的一部分,但我遇到的每个 C/C++ 编译器(很多)都使用它。这个函数基本上做了三件事:

  1. 初始化 CRT
  2. 调用 main
  3. 拆卸

在上面的例子中,调用的是 _tmain,但这是一些宏魔法,允许“main”可以具有各种形式,其中一些是 VS 特定的案件。

上述过程的作用是从 CRT 中删除模块“crt0”并用新模块替换。这就是为什么您不能使用运行时 DLL,该 DLL 中已经存在一个函数,其入口点名称与我们添加的函数 (2) 相同。当您静态链接时,CRT 是 .lib 文件的集合,并且链接器允许您完全覆盖 .lib 模块。在这种情况下,一个模块只有一个功能。

我们的新程序包含库存 CRT,减去其 CRT0 模块,但带有我们自己创建的 CRT0 模块。在那里我们删除了对 main 的调用。所以哪里都没有主线!

(2) 您可能认为可以通过重命名 crt0.c 文件中的入口点函数并更改链接器设置中的入口点来使用运行时 DLL。但是,编译器不知道入口点更改,并且 DLL 包含对您未提供的“主”函数的外部引用,因此它无法编译。

Yes! You can do away with main.

Disclaimer: You asked if it were possible, not if it should be done. This is a totally un-supported, bad idea. I've done this myself, for reasons that I won't get into, but I am not recommending it. My purpose wasn't getting rid of main, but it can do that as well.

The basic steps are as follows:

  1. Find crt0.c in your compiler's CRT source directory.
  2. Add crt0.c to your project (a copy, not the original).
  3. Find and remove the call to main from crt0.c.

Getting it to compile and link can be difficult; How difficult depends on which compiler and which compiler version.

Added

I just did it with Visual Studio 2008, so here are the exact steps you have to take to get it to work with that compiler.

  1. Create a new C++ Win32 Console Application (click next and check Empty Project).
  2. Add new item.. C++ File, but name it crt0.c (not .cpp).
  3. Copy contents of C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src\crt0.c and paste into crt0.c.
  4. Find mainret = _tmain(__argc, _targv, _tenviron); and comment it out.
  5. Right-click on crt0.c and select Properties.
  6. Set C/C++ -> General -> Additional Include Directories = "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\crt\src".
  7. Set C/C++ -> Preprocessor -> Preprocessor Definitions = _CRTBLD.
  8. Click OK.
  9. Right-click on the project name and select Properties.
  10. Set C/C++ -> Code Generation -> Runtime Library = Multi-threaded Debug (/MTd) (*).
  11. Click OK.
  12. Add new item.. C++ File, name it whatever (app.cpp for this example).
  13. Paste the code below into app.cpp and run it.

(*) You can't use the runtime DLL, you have to statically link to the runtime library.

#include <iostream>

class App
{
    public: App()
    {
        std::cout << "Hello, World! I have no main!" << std::endl;
    }
};

static App theApp;

Added

I removed the superflous exit call and the blurb about lifetime as I think we're all capable of understanding the consequences of removing main.

Ultra Necro

I just came across this answer and read both it and John Dibling's objections below. It was apparent that I didn't explain what the above procedure does and why that does indeed remove main from the program entirely.

John asserts that "there is always a main" in the CRT. Those words are not strictly correct, but the spirit of the statement is. Main is not a function provided by the CRT, you must add it yourself. The call to that function is in the CRT provided entry point function.

The entry point of every C/C++ program is a function in a module named 'crt0'. I'm not sure if this is a convention or part of the language specification, but every C/C++ compiler I've come across (which is a lot) uses it. This function basically does three things:

  1. Initialize the CRT
  2. Call main
  3. Tear down

In the example above, the call is _tmain but that is some macro magic to allow for the various forms that 'main' can have, some of which are VS specific in this case.

What the above procedure does is it removes the module 'crt0' from the CRT and replaces it with a new one. This is why you can't use the Runtime DLL, there is already a function in that DLL with the same entry point name as the one we are adding (2). When you statically link, the CRT is a collection of .lib files, and the linker allows you to override .lib modules entirely. In this case a module with only one function.

Our new program contains the stock CRT, minus its CRT0 module, but with a CRT0 module of our own creation. In there we remove the call to main. So there is no main anywhere!

(2) You might think you could use the runtime DLL by renaming the entry point function in your crt0.c file, and changing the entry point in the linker settings. However, the compiler is unaware of the entry point change and the DLL contains an external reference to a 'main' function which you're not providing, so it would not compile.

无人问我粥可暖 2024-10-10 18:54:45

一般来说,应用程序需要一个入口点,而 main 就是该入口点。全局变量的初始化可能发生在 main 之前这一事实几乎是无关紧要的。如果您正在编写控制台或 GUI 应用程序,则必须有一个 main 才能链接它,并且让该例程负责应用程序的主要执行而不是使用其他例程是唯一的好习惯。用于奇怪的非预期目的的功能。

Generally speaking, an application needs an entry point, and main is that entry point. The fact that initialization of globals might happen before main is pretty much irrelevant. If you're writing a console or GUI app you have to have a main for it to link, and it's only good practice to have that routine be responsible for the main execution of the app rather than use other features for bizarre unintended purposes.

妳是的陽光 2024-10-10 18:54:45

嗯,从C++标准的角度来看,是的,它仍然是必需的。但我怀疑你的问题的性质与此不同。

我认为按照你的想法去做会导致太多问题。

例如,在许多环境中,main 的返回值作为整个程序运行的状态结果给出。这真的很难从构造函数中复制。当然,某些代码仍然可以调用 exit,但这似乎使用 goto 并且会跳过对堆栈上任何内容的破坏。您可以尝试通过抛出特殊异常来修复问题,以生成除 0 之外的退出代码。

但这样你仍然会遇到全局构造函数的执行顺序未定义的问题。这意味着在全局对象的任何特定构造函数中,您将无法对是否存在任何其他全局对象做出任何假设。

您可以尝试通过仅说每个构造函数都有自己的线程来解决构造函数顺序问题,并且如果您想访问任何其他全局对象,则必须等待条件变量,直到它们说它们已构造。但这只会导致死锁,而这些死锁将很难调试。您还会遇到这样的问题:哪个线程因特殊的“程序返回值”异常而退出将构成整个程序的实际返回值。

我认为如果你想摆脱 main,这两个问题是杀手。

我想不出有一种语言没有与 main 相同的基本功能。例如,在 Java 中,有一个外部提供的类名,它的 main 静态函数被调用。在 Python 中,有 __main__ 模块。在 perl 中,有您在命令行上指定的脚本。

Well, from the perspective of the C++ standard, yes, it's still required. But I suspect your question is of a different nature than that.

I think doing it the way you're thinking about would cause too many problems though.

For example, in many environments the return value from main is given as the status result from running the program as a whole. And that would be really hard to replicate from a constructor. Some bit of code could still call exit of course, but that seems like using a goto and would skip destruction of anything on the stack. You could try to fix things up by having a special exception you threw instead in order to generate an exit code other than 0.

But then you still run into the problem of the order of execution of global constructors not being defined. That means that in any particular constructor for a global object you won't be able to make any assumptions about whether or not any other global object yet exists.

You could try to solve the constructor order problem by just saying each constructor gets its own thread, and if you want to access any other global objects you have to wait on a condition variable until they say they're constructed. That's just asking for deadlocks though, and those deadlocks would be really hard to debug. You'd also have the issue of which thread exiting with the special 'return value from the program' exception would constitute the real return value of the program as a whole.

I think those two issues are killers if you want to get rid of main.

And I can't think of a language that doesn't have some basic equivalent to main. In Java, for example, there is an externally supplied class name who's main static function is called. In Python, there's the __main__ module. In perl there's the script you specify on the command line.

初与友歌 2024-10-10 18:54:45

如果要构造多个全局对象,则无法保证哪个构造函数将首先运行。

If you have more than one global object being constructed, there is no guarantee as to which constructor will run first.

顾铮苏瑾 2024-10-10 18:54:45

如果您正在构建静态或动态库代码,那么您不需要自己定义 main ,但您仍然会在某个包含它的程序中运行。

If you are building static or dynamic library code then you don't need to define main yourself, but you will still wind up running in some program that has it.

厌倦 2024-10-10 18:54:45

如果您正在为 Windows 编写代码,请不要这样做。

完全从全局对象的构造函数中运行您的应用程序可能会在相当长的一段时间内工作得很好,但迟早您会调用错误的函数并最终得到一个在没有警告的情况下终止的程序。

  1. 全局对象构造函数在 C 运行时启动期间运行。
  2. C 运行时启动代码在 C 运行时 DLL 的 DLLMain 期间运行
  3. 。在 DLLMain 期间,您持有 DLL 加载器锁。
  4. 在已经持有 DLL 加载器锁的情况下尝试加载另一个 DLL 会导致进程迅速死亡。

将整个应用程序编译成单个可执行文件并不能拯救您——许多 Win32 调用都有可能悄悄加载系统 DLL。

If you are coding for windows, do not do this.

Running your app entirely from within the constructor of a global object may work just fine for quite awhile, but sooner or later you will make a call to the wrong function and end up with a program that terminates without warning.

  1. Global object constructors run during the startup of the C runtime.
  2. The C runtime startup code runs during the DLLMain of the C runtime DLL
  3. During DLLMain, you are holding the DLL loader lock.
  4. Tring to load another DLL while already holding the DLL loader lock results in a swift death for your process.

Compiling your entire app into a single executable won't save you - many Win32 calls have the potential to quietly load system DLLs.

萌酱 2024-10-10 18:54:45

在某些实现中,全局对象是不可能的,或者对于此类对象来说,非平凡的构造函数是不可能的(特别是在移动和嵌入式领域)。

There are implementations where global objects are not possible, or where non-trivial constructors are not possible for such objects (especially in the mobile and embedded realms).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文