57 ms·
Main is usually a function. So then when is it not? (2015)
- Kab1r 3y ago(2015)
- smokel 3y agoBack when compiler warnings possibly cost extra processing time to generate, it was possible to make gcc compile the craziest things. After much experimentation, it turned out that the smallest program that would compile and run was only 5 bytes long: main;
- LoganDark 3y ago> make gcc compile the craziest things In languages like Haskell you can just make 2+2=5: https://codegolf.stackexchange.com/a/28794 https://codegolf.stackexchange.com/a/28794
- extraduder_ire 3y agoI still get a segmentation fault out of that when I run it. I think there's flags you can use to get the linker to not complain about missing main if you give gcc an empty file. The craziest thing I got gcc (the AVR version, specifically) to compile for real purposes was a preprocessor macro that spit out tens of thousands of asm blocks with memory barriers and nop (do nothing) instructions with "PORTB = 1;" in the middle and "PORTB = 0" at the end. I needed it to bitbang out a clock signal to read an RFID tag on poorly documented hardware. (fun fact: the clock the cpu uses on an arduino uno is considerably worse and less accurate than the one that's on the board for the USB chip.)
- SomeoneFromCA 3y agoMust be the internal RC clock? it sucks.
- extraduder_ire 3y agoNope, it's an external one at 16mhz. I think the internal one on the chip can only get up to 8mhz, and is accurate to about +-5% It's enough to work for most things. Only annoying thing is, if you use the ICSP header to set the clock to external, you need a working clock to even use ICSP. (the 328pb falls back to the internal clock in this case)
- leblancfg 3y agoWelp that's a nerd snipe if I ever had one. Will report back once my bruteforce hack finishes crunching away.
- smokel 3y agoPlease do, I'm eager to find out how long it takes to compile and run 4,294,967,296 small C files on modern hardware ;)
- leblancfg 3y agoI was able to feed stdin to `gcc` instead of all that IO, which considerably sped it up and spread over 5 cores. But ultimately I gave up after the third time my computer crashed. I was at length 4 by that point, next step would have been to throttle the CPU usage.
- 10000truths 3y agoThis won't work anymore, as compilers will now place const arrays in the .rodata section, which is non-executable. Luckily, there's an easy fix - just qualify the declaration of the array with: __attribute__((section(".text"))
- arlcode 3y agoIn a way that's unfortunate because the "google keyword" .text gives away what is happening here. I bet there are some people who wouldn't even think about the fungible nature of data types (especially if they come from a "modern" language).
- muxator 3y agoComplete final program for the laziest of us (after incorporating @10000truths's advice): const int main[] __attribute__ ((section(".text"))) = { -443987883, 440, 113408, -1922629632, 4149, 899584, 84869120, 15544, 266023168, 1818576901, 1461743468, 1684828783, -1017312735 }; Compilation (gcc 13): $ gcc -Wall main.c -o main main.c:1:11: warning: ‘main’ is usually a function [-Wmain] 1 | const int main[] __attribute__ ((section(".text"))) = { | ^~~~ /tmp/ccsWmdiD.s: Assembler messages: /tmp/ccsWmdiD.s:4: Warning: ignoring changed section attributes for .text Execution: $ ./main Hello World!
- CueXXIII 3y agoDoesn't work, it prints nothing (on gcc (Debian 13.2.0-1) 13.2.0). Although I found that gcc seems to be configured with -pie per default, so this compilation works: $ gcc -Wall -fno-PIE -no-pie main.c -o main mainf.c:1:11: warning: ‘main’ is usually a function [-Wmain] 1 | const int main[] __attribute__((section(".text"))) = { | ^~~~ /tmp/ccq0adwj.s: Assembler messages: /tmp/ccq0adwj.s:4: Warning: ignoring changed section attributes for .text
- orra 3y agoThe article says `lea` helps calculate the array relative address on AMD64. Why does the article say the problem would be tricky on 32-bit? `lea` is an old instruction. Thanks.
- astrange 3y agox86-32 doesn't have PC-relative addressing (`%eip`). Btw, his code is wrong, it assumes pointers fit into 32 bits.
- comex 3y agoIt’s not `lea` itself that’s new in AMD64 but rather RIP-relative addressing; this can be used as a memory operand for any instruction but it’s especially useful with `lea`. If you’re wondering where the reference to RIP is, the assembly in the post uses %eip instead of the usual %rip. [Edit: And doing so is wrong, contrary to what I wrote before editing; see sibling comment.]
- eimrine 3y agomain; is a valid C program but main is not a function here.
- deleted 3y ago[deleted]
- noduerme 3y agoThis is fun. On a broader note though, entry point main-type functions have always bothered me. Maybe because I grew up as a simple script kiddie with BASIC and Bash and PHP. I like code that starts executing from the first line and then runs whatever functions it wants to run. I realize that's just an abstraction of main() but it's a pleasant one for me. There's something more constricting about there being one function to bootstrap everything than there is about one file.
- ekidd 3y ago> There's something more constricting about there being one function to bootstrap everything than there is about one file. As a compiler author, there are a bunch of nasty surprises to this approach. If you execute a file line-by-line, then functions only exist once you "reach" them. If you write: def a(): b() a() def b(): ... ...then a() needs to crash when first called, because b() hasn't been declared yet. So your functions need to be invoked via some kind of table, and can't easily use jumps to hard-coded offsets. And b() can't be inlined. There are dozens of these problems that come up when generating efficient code. And the easiest way to fix them all is to make your entire program "exist" from the beginning, so it can be compiled and optimized as a whole. Which is how you wind up with main().
- noduerme 3y agoThat certainly makes sense, as far as main() goes, but having main() in C++ for example doesn't solve the problem of needing to define functions prior to calls to them. It just helps you put all your stuff before main(), but even then it only "exists" if it shows up in the right order in every previous header file. Line-based stuff like BASIC's GOTO/GOSUB was kind of a fun workaround to the idea of even having functions at all, and I'd happily live in that place still... but... as somoene who manifestly does not write compilers, is it still an extravagant demand in this day and age to ask a compiler to check and include/throw for all the functions out-of-order before running a code file from the top? [edit] What I mean is, this was fundamental to ES3/4 bytecode compilers for VMs like Java or Flash, and it would be absurd to ask e.g. Javascript coders [edit: said Python, don't work with Python, there are cases in PHP where it's necessary] to order their functions in the order they're invoked with virtuals to place them. This is like providing toilets on a cruise ship. It's barely even a "service", like whereas garbage collection or something is an actual service. Restructuring the order of function definitions as you build out dependencies has got to be one of the worst wastes of coder time I can think of. Great if you really can find an optimization by doing that, but the way people code these days that basically never happens. We have to assume that everything we write gets compiled anyway, or why else is it in the code! If there are no dead branches, what's the purpose of not "existing"/let's say pre-virtualizing all the branches, regardless of which order they're written in? (Serious question).
- picadores 3y agoWe built a embedded "Operating System" during CS science courses, that was basically just a continous recursion, were the stack was eliminated and reset repeatetly. Main was just some assembly manipulating the instruction pointer to get the whole thing rolling. Good times, good crimes.
- bee_rider 3y ago5 points off for coding style. 5 points off for assuming too much about the system.
- eschneider 3y agoLooks like he's independently re-invented PEEK and POKE from BASIC.
- gumby 3y agoWell, if you really want to confound things you could write your own __start which is typically the entry point called by the kernel, which in turn calls `main()`. But actually that's just by convention: C (and C++) programs drop `__start` into the binary and then tell the linker to mark the binary (mostly an ELF file these days, thank goodness) to indicate that the entry point is the symbol `__start`. There can be different versions depending on how many arguments to `main()` are requested, or it can simply pass all three and let the function body ignore the unwanted ones. If you really want to confuse the TA, write the following program: #include <stdio.h> void foo(); int main() { printf ("I'm in main\n"); foo(); printf ("I'm still in main\n"); } void foo() { printf ("I'm in foo!\n"); } Then tell the linker to mark `foo` as your entry point. You could do this from the command line or even a custom linker script. Of course depending on what your OS needs from `__start` you may have to do some extra setup, sorry. But this might work on Linux. I've actually been thinking of modifying gcc's `__start` to pass the arguments and env vars as a span (well, the function __start calls that calls `main()`.)
- ackfoobar 3y agoThis reminds me of James Iry saying "all your code and data are in one giant mutable array indexed by pointers, good luck". Half true, but insightful.
- noam_k 3y agoThis reminds me of a great riddle: what is the shortest C code that compiles, but segfaults? With the right flags: `main;` (implicitly a zeroed int).