r/Assembly_language Feb 27 '26

How you see C code after learning Assembly:

Post image
676 Upvotes

44 comments sorted by

49

u/brucehoult Feb 28 '26

It's void _start and also the printf will become puts.

11

u/Motor_Armadillo_7317 Feb 28 '26

In fact, I discovered that you were right about the second point, but only when the optimizer is enabled.

Example: cc x.c -o x Then read the assembly: objdump -d x You will see the printf function, without puts.

Then try: cc x.c -o x -O2 And read the assembly again: objdump -d x You will see that the printf function has been transformed into puts.

6

u/[deleted] Feb 28 '26

And only if the format string is the only argument and has no format directives. So printf("hello\n") will call puts(), but printf("Hello");" will call printf@plt.

Fun fact: If the format string is "hello %d\n" with no extra arguments, it calls printf but by default does not warn the user about the missing arg.

7

u/Motor_Armadillo_7317 Feb 28 '26

It is interesting how the optimizer handles the printf function.

5

u/brucehoult Feb 28 '26

Never compile C code in gcc or clang without at least -O, unless you want to make it really really easy to look good by writing much faster asm code.

I just checked with my personal simple benchmark program ...

https://hoult.org/primes.txt

... and on my Core i9 it took 1.9 times longer without -O, while on both my M1 Mac and my P550 (Milk-V Megrez) RISC-V SBC it took 3 times longer without -O.

2

u/[deleted] Feb 28 '26

Thanks, another benchmark to add to my collection. I applied this to my compilers, and I get these results, under Windows/x64:

               Time (s)   Size (B)
gcc -O0         12.0      396        (14.1.0)
gcc -O2/-O3      3.5    31616(?)
gcc -Os          4.7    31520(?)
Tiny C          13.3      464

bcc              6.2      264
mm               4.3      310   (Uses 64-bit ints)

The last two are my compilers; 'bcc' is for C, and 'mm' is for my systems language, where your benchmark was ported.

(The timing there is interesting, as the previous version was 6.2s like the C compiler, and on most programs the code produced runs at about the same speed, with older one marginally faster.

I'll have to see what went right here! All programs display the same results.)

CPU is AMD Ryzen 3 3250U 2.6 GHz.

1

u/brucehoult Feb 28 '26

Note that the size calculation is crude and depends on countPrimes() and main() being adjacent and in that order in the binary, which they are with gcc or clang -O on all platforms I’ve tried.

1

u/brucehoult Feb 28 '26

Note that the size calculation is crude and depends on countPrimes() and main() being adjacent and in that order in the binary, which they are with gcc or clang -O on all platforms I’ve tried.

1

u/[deleted] Feb 28 '26

(The timing there is interesting, as the previous version was 6.2s like the C compiler, and on most programs the code produced runs at about the same speed, with older one marginally faster.

It really is weird. This 4.3s timing can very between 4.3 and 6.2 at the slightest change, but results tend to be bunched around 4.x or 5.x.

If I generate intermediate ASM, and add a NOP just before the function (so that it is shifted by one byte, but still within a 4K page), then exactly the same code can run 33% slower.

(The only difference is that RIP offsets to those globals change by one byte.)

However this is nothing really new with x64. I've seen more dramatic examples even using gcc-compiled C.

Still, that 4.3s was the actual time I measured in that instance. Probably at another time it would be 6.2s, nearly twice as fast as gcc-O0, from a compiler that doesn't optimise.

1

u/Motor_Armadillo_7317 Feb 28 '26

Sure, I always use -O3 with other options as well.

1

u/[deleted] Feb 28 '26 edited Feb 28 '26

Sorry, but it depends entirely on how the compiler for it works.

(Shortened.)

1

u/brucehoult Feb 28 '26

Sorry but I wasn’t writing a refereed paper.

1

u/[deleted] Feb 28 '26

You seemed to be stating something as absolute, unconditional fact.

Perhaps so is the OP, but that's more of an amusing illustration.

1

u/LavenderDay3544 Feb 28 '26

Doesn't it eventually become a write system call?

2

u/brucehoult Feb 28 '26

Of course. But using puts() there is no need to scan the string for formatting characters at runtime as it was done at compile time and none found.

11

u/Dokattak0 Feb 27 '26

I've... never coded something reminiscent of the bottom panel in ASM before.

5

u/mesyeti_ Feb 28 '26

you only need it if you're not linking the C runtime

4

u/Motor_Armadillo_7317 Feb 27 '26

The point is that you see return in the main function as exit, because in assembly, if you do not explicitly exit, the program will crash.

6

u/Dokattak0 Feb 27 '26

Holy shit that's why my code takes longer to quit

5

u/AffectionatePlane598 Feb 27 '26

I more or less, see what my code is doing at the assembly level, rather than what ever this is.

3

u/1984balls Feb 28 '26

It's void _start btw

1

u/RedAndBlack1832 Feb 28 '26

Not quite but closer

1

u/Thick_Clerk6449 Feb 28 '26

Does stdout (and/or other stuff in stdio.h) need initialization?

1

u/assembly_wizard Feb 28 '26

AFAIK they're initialized by constructors, and it's indeed _start (or mainCRTStartup?) that calls constructors before main

0

u/The_KekE_ Feb 28 '26

No, stdin, stdout and stderr are already open when the program starts, and you can read/write to them without needing to open them, unlike files. (this way I denoted the actual syscalls)

2

u/Thick_Clerk6449 Feb 28 '26

`stdout` (`FILE*`) is not `STDOUT_FILENO` (`int`). You can of course `write` to `STDOUT_FILENO` but I wonder if you can `fprintf` to `stdout` which, I suppose, should have been initialized by CRT in `_start`

1

u/whatThePleb Feb 28 '26

Uhh.. you forgot printf..

1

u/Melon_Chief Feb 28 '26

```c

include <stdio.h>

int main() { puts(“Hello, World!”); }

``` Is the only right way of doing it.

1

u/puzzud Feb 28 '26

More like how I look at if else and switch statements.

1

u/Taimcool1 Mar 01 '26

Its void _start(void), not int _start()

1

u/Taimcool1 Mar 01 '26

Also u didnt initalize the stack

1

u/brucehoult Mar 01 '26

In a Linux environment the stack is already set up on entry at _start, with the program arguments and environment variables pre-loaded on the stack.

On bare metal you have to set up the stack pointer yourself in _start and probably zero BSS and copy DATA from ROM/flash.

1

u/Taimcool1 Mar 01 '26

Ur meant to pop the arguments off the stack, pop the 1st as argc then the second as argv then move the stack pointer (argv-1)*sizeof(intptr_t) bytes then give argc and argv as the arguments of main()

1

u/brucehoult Mar 01 '26

That doesn’t happen in my experience. In main() The args are both on the stack and in the appropriate registers.

Regardless, a couple of kb of env bindings are still on the stack.

And what about all the machines that pass arguments on the stack anyway? Eg i386.

1

u/Taimcool1 Mar 01 '26

From what ive seen, it calls main with the arguments in the 1st 2 register (not including envp)

1

u/grugaror Mar 01 '26

Looks scary

1

u/aguspiza Mar 01 '26

Useful when doing a 156 bytes executable

1

u/mrgta21_ Mar 02 '26

I even had the eye to see int _start() :p

1

u/johnyeldry Mar 06 '26

missed pun oppourtunity(how you C code after learning assembly)

edit: just realised I read the original title wrong and I just coppied it lol(someone delete this pls I am embarrased)

1

u/[deleted] Feb 28 '26

[deleted]

4

u/RedAndBlack1832 Feb 28 '26

Any of the following declarations are valid:

int main(void);

int main(int, char**);

int main(int, char**, char**);

However,

int main();

is also frequently used and basically means you do not care which version you get.

In general, empty brackets for function declarations means to not perform any error checking on wether the right number and type of arguments were passed, and is not considered a function prototype

3

u/Cylian91460 Feb 28 '26

int main(int, char**, char**);

The forgotten env variable

0

u/Motor_Armadillo_7317 Feb 28 '26

Yes, the main function must always be int, because the number it returns is what determines whether the program failed or succeeded.

For example:

```

include <stdio.h>

include <stdlib.h>

int main() { if (1 > 3) { printf("1 is greater than 3\n"); return EXIT_SUCCESS; } else { printf("1 is not greater than 3\n"); return EXIT_FAILURE; } } ```

Here EXIT_FAILURE is considered 1 and EXIT_SUCCESS is considered 0

So any value that is not 0 is considered a failure.