
C Internals - ingve
http://www.avabodh.com/cin/cin.html
======
e19293001
For those who are interested to hone their skills in translating C to assembly
language just for the sake of grokking a C compiler, you might want to read
this book:

Assembly language and computer architecture using C++ and Java Book by Anthony
J. Dos Reis

From book's description:

Students learn best by doing, and this book supplies much to do with various
examples and projects to facilitate learning. For example, students not only
use assemblers and linkers, they also write their own. Students study and use
instruction sets to implement their own. The result is a book that is easy to
read, engaging, and substantial.

I'm not affiliated with the author though. This book helped a lot in my career
as a hardware and firmware engineer.

It talks about not only C language but C++. You'll be able to translate a C++
class into assembly language by hand. The drawback is that you'll learn a
hypothetical CPU but the concepts are still the same with the real world CPU.
C internals could supplement it as well.

~~~
thr0w__4w4y
I can’t improve upon your recommendation, but I’ll second it, from the point
of a firmware / security / reverse engineer-er.

~~~
jart
[https://gist.github.com/jart/fe8d104ef93149b5ba9b72912820282...](https://gist.github.com/jart/fe8d104ef93149b5ba9b72912820282c)

------
_kst_
[http://www.avabodh.com/cin/programstructure.html](http://www.avabodh.com/cin/programstructure.html)

    
    
        ...
        void main()
        ...
    

That's where I stopped reading.

Since this comment is getting downvotes, I'll explain. The "void" keyword was
introduced in the 1989 ANSI C standard. That same standard specified that the
valid definitions for the main functions are:

    
    
        int main(void) { /* ... */ }
    

and

    
    
        int main(int argc, char *argv[]) { /* ... */ }
    

or equivalent (or implementations can support other forms). There has never
been a version of C in which "void main()" is a valid way to define the main
function.

It's a small detail, and yes, many compilers will let you get away with it
(the language doesn't require a diagnostic), but anyone writing about C should
be aware of this, and should set a good example by writing correct code.

Maybe the site is OK other than that, but it doesn't inspire confidence.

~~~
DoingIsLearning
Just for some context, the standard specifies an int return type for the
purpose of returning a meaningful value back to a hosting operating system.

However, in the specific context of stand-alone or freestanding programs, the
"void main()" definition would be absolutely nonconsequential, since there is
no host to return a value to.

~~~
tom_mellior
> However, in the specific context of stand-alone or freestanding programs,
> the "void main()" definition would be absolutely nonconsequential

One possible consequence I can think of: If your program is safety-critical
(or even otherwise) you might be interested in running static analysis or
verification tools on it. These tools implement the C standard, so they would
emit a diagnostic on the use of void main.

Other than that, sure, the language police will not come and break down your
door. If your compiler's docs say that it accepts this construct with the
meaning you want, it is indeed an inconsequential, though also _completely
unnecessary_ , deviation from the standard.

------
pjmlp
while very educative, it just teaches one possible translation from C.

Lets pick the "Translation of Arithmetic Operations" example and convert it to
float instead of int

    
    
        int a = 2;
        int b= 3;
        int c = 24;
        a = a + b;
        a = a + b * c;
    

[http://www.avabodh.com/cin/arithmeticop.html](http://www.avabodh.com/cin/arithmeticop.html)

And pack it into a function so that Golbolt can compile it.

    
    
        float hn_demo(void) {
          float a = 2;
          float b = 3;
          float c = 24;
          a = a + b;
          a = a + b * c;
          return a;
        }
    

And now pick a CPU that isn't that good with floating point, like AVR, using
GCC 4.5.4, and we get:

    
    
            ldi r24,lo8(0x40000000)
            ldi r25,hi8(0x40000000)
            ldi r26,hlo8(0x40000000)
            ldi r27,hhi8(0x40000000)
            std Y+1,r24
            std Y+2,r25
            std Y+3,r26
            std Y+4,r27
            ldi r24,lo8(0x40400000)
            ldi r25,hi8(0x40400000)
            ldi r26,hlo8(0x40400000)
            ldi r27,hhi8(0x40400000)
            std Y+5,r24
            std Y+6,r25
            std Y+7,r26
            std Y+8,r27
            ldi r24,lo8(0x41c00000)
            ldi r25,hi8(0x41c00000)
            ldi r26,hlo8(0x41c00000)
            ldi r27,hhi8(0x41c00000)
            std Y+9,r24
            std Y+10,r25
            std Y+11,r26
            std Y+12,r27
            ldd r22,Y+1
            ldd r23,Y+2
            ldd r24,Y+3
            ldd r25,Y+4
            ldd r18,Y+5
            ldd r19,Y+6
            ldd r20,Y+7
            ldd r21,Y+8
            rcall __addsf3
            mov r27,r25
            mov r26,r24
            mov r25,r23
            mov r24,r22
            std Y+1,r24
            std Y+2,r25
            std Y+3,r26
            std Y+4,r27
            ldd r22,Y+5
            ldd r23,Y+6
            ldd r24,Y+7
            ldd r25,Y+8
            ldd r18,Y+9
            ldd r19,Y+10
            ldd r20,Y+11
            ldd r21,Y+12
            rcall __mulsf3
            mov r27,r25
            mov r26,r24
            mov r25,r23
            mov r24,r22
            mov r18,r24
            mov r19,r25
            mov r20,r26
            mov r21,r27
            ldd r22,Y+1
            ldd r23,Y+2
            ldd r24,Y+3
            ldd r25,Y+4
            rcall __addsf3
            mov r27,r25
            mov r26,r24
            mov r25,r23
            mov r24,r22
            std Y+1,r24
            std Y+2,r25
            std Y+3,r26
            std Y+4,r27
    

Which includes calls to a floating point emulation library and quite different
from the x86 example, with numbers using multiple registers.

So more of an heads up, sometimes the C translation to Assembly isn't as
direct as one might think.

~~~
hoseja
Perhaps more telling/less nit-pickable is when you modify the function to be

    
    
        float hn_demo(float a, float b, float c) {
          a = a + b;
          a = a + b * c;
          return a;
        }
    

and let the compiler actually optimize with -02.

~~~
pjmlp
Sure, but it still boils down to the fact that it isn't as 1:1 as many think
it is.

------
mangamadaiyan
Minor quibble:

The section on local variables assumes a downward-growing stack. This is
completely fair, because the introduction specifies that the articles deal
with an x86 world. What gets missed out is the fact that the direction of
stack growth is determined by the processor :)

This is not really a complaint ... it just seemed to me like a missed
opportunity to mention something interesting.

~~~
userbinator
Try to name one processor with an upward-growing stack... I bet most people
who are otherwise familiar with this material can't.

(I'm not saying you're wrong --- I personally know two ;-)

~~~
mangamadaiyan
SPARC and ARM are the only ones I know, and the SPARC is the only one I've
worked with in a past life. (Edit: SPARC allowed you to decide the direction
of stack growth, it wasn't really fixed. I've heard that ARM does the same. So
in some sense, I didn't really answer your question :) ).

~~~
pm215
Original Arm had an ISA which didn't mandate a stack direction -- the stack
pointer register (r13) was not special and it was only the calling convention
that decreed that it was the stack pointer. The instruction set's LDM/STM
(load/store multiple) insns supported both decrementing and incrementing the
base register either before or after the accesses, which meant you could use
them to implement an ascending stack if you wanted. However in practice the
usual calling convention was "r13 is the stack pointer, and the stack
descends". When the Thumb instruction set was added, this convention was baked
into some instructions because the 16-bit opcode size didn't allow spending
bits on the fully flexible "any base register, any direction, any way"
operations the 32-bit Arm encoding used. In the M-profile variant of the
architecture, the calling convention is heavily baked into the exception
handling model (taking an exception/interrupt pushes a stack frame onto the
stack) so the stack must be r13 and grow downwards. In the 64-bit Arm
architecture, SP is no longer a general purpose register; I think in theory
you could implement an ascending stack, but since the [reg+offset] addressing
mode that allows the largest offset value takes an unsigned offset, accessing
stack slots via [sp+N] is more natural than the [sp-N] an ascending stack
would prefer.

Summary: original Arm gave the software the flexibility to implement an
ascending stack, but in practice the stack was always descending, and more
recent architecture changes assume this to a greater or lesser extent.

~~~
mangamadaiyan
Thank you, I learned something new today!

~~~
joosters
It's not just the direction of the stack: in theory, there are two possible
kinds of downwards-growing stacks, a 'full' SP points to the last used entry,
whereas an 'empty' SP points to the next free entry. It all depends upon how
you 'push' something on to the stack: do you decrease the SP before or after
writing the data?

ARM let you choose either approach (making four different stack configurations
in total!) - this flexibility is because ARM didn't have any specialised
'push' or 'pop' operations, you read/write to the stack using the normal
load/store ops, which have a variety of addressing modes.

------
exmadscientist
The use of AT&T syntax here is unfortunate. When the processor documentation
and every single other toolchain out there uses a different syntax, you should
use that syntax too.

EDIT: Would any of the people downvoting this comment care to explain their
affection for this historical mistake? I have never understood why anyone
would choose it.

~~~
saagarjha
> When the processor documentation and every single other toolchain out there
> uses a different syntax, you should use that syntax too.

All the GNU tools, and many of their clones, use AT&T syntax. I think I run
into it more often than Intel, and I turn on the option for the latter where I
can. It’s really prevalent.

~~~
rrmm
Same for me. Usually just because of the ubiquity of gcc and its ilk.

------
dannas
Per Vognsens description of how a compiler can lower C statements might be of
interest! He lowers the code to RISC-V but the techniques and explanations is
platform-independent.

[https://raw.githubusercontent.com/pervognsen/bitwise/master/...](https://raw.githubusercontent.com/pervognsen/bitwise/master/notes/asm.txt)

------
z29LiTp5qUC30n
For someone who wants to know how a C compiler converts C to assembly there is
no better example than:

[https://github.com/oriansj/mescc-tools-
seed/blob/master/x86/...](https://github.com/oriansj/mescc-tools-
seed/blob/master/x86/NASM/cc_x86.S)

------
freecodyx
i learned those common patterns by using godbolt.org

------
staycoolboy
I think this is a smart way to introduce C.

------
vmchale
Thank you! This is very interesting.

