Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does a function look like in memory? [closed]

I'm starting to learn C, and I just learned about pointers to functions. This made me wonder, what does a function look like in memory? I mean, (correct me if I'm wrong) an int variable for example is just a chunk of 2/4 bytes, storing a certain whole number. But what does a function look like in memory? How do you convert operations such as if, while, malloc, etc into numbers or binary?

like image 324
roeegg Avatar asked Sep 11 '26 20:09

roeegg


2 Answers

What does a function look like in memory?

A function is a number of consecutive bytes. For instance in hex a function could be:

A2 32 67 98 34 33 12 23 22 55

These (hex) numbers represents instruction that the hardware, i.e. the CPU, can execute. The CPU decodes the values and finds out what it is going to do. In other words - which instructions to execute.

Exactly how these numbers translate into instructions differs from CPU family to CPU family. Sometimes a single byte is a whole instruction. Sometimes it takes a number of bytes to make an instruction. To figure out how to translate the bytes into instructions, you need to consult the instruction set of the CPU being used.

In some cases instructions are pretty simple. For instance "ADD register 1 and register 2", "JUMP-IF-ZERO register 3, address", "DECREMENT register 5", "LOAD register 1 from a memory address", "STORE register 1 to a memory address" and so on. But you can also find CPUs with pretty complex instructions.

? How do you convert operations such as if, while,

By using a number of the simple instructions from the instruction set.

Example: With the simple instructions I made up above and assuming that x is integer located at address 0x100 in memory, the C code:

x = x - 1;
if (x != 0)
{
    x = x - 1;
}

could be something like

    LOAD reg1, 0x100  // Read x from memory address 0x100 into register 1
    DECREMENT reg1
    JUMP-IF-ZERO reg1, LABEL1
    DECREMENT reg1
LABEL1:
    STORE reg1, 0x100 // Write x back to memory

Notice that a specific C code often can be made in several ways on a specific CPU. Different compilers may generate a different sequence of instruction for the same C code.

like image 82
Support Ukraine Avatar answered Sep 14 '26 11:09

Support Ukraine


The answer to your question is machine code.

Run your compiled code in a source-level debugger and switch on the "disassembly view" to see exactly how the source is translated to machine code (typically presented as a "disassembly" which is a human readable representation of the machine code - "assembly code"). Or use Compiler Explorer which presents the assembly code generated for many different compilers for various architectures and machine instruction sets. For example Compiler Explorer shows that the following C source code:

int fn(void)
{
    return 11 ;
}

int main() 
{
    int (*fn_ptr)(void) = fn ;
    int val = fn_ptr() ;
    if( val > 10 )
    {
        val = 10 ;
    }    

    return 0 ;
}

using MSVC v19 x64 compilation translates to:

fn      PROC
        mov     eax, 11
        ret     0
fn      ENDP

val$ = 32
fn_ptr$ = 40
main    PROC
$LN4:
        sub     rsp, 56                             ; 00000038H
        lea     rax, OFFSET FLAT:fn
        mov     QWORD PTR fn_ptr$[rsp], rax
        call    QWORD PTR fn_ptr$[rsp]
        mov     DWORD PTR val$[rsp], eax
        cmp     DWORD PTR val$[rsp], 10
        jle     SHORT $LN2@main
        mov     DWORD PTR val$[rsp], 10
$LN2@main:
        xor     eax, eax
        add     rsp, 56                             ; 00000038H
        ret     0
main    ENDP

Each assembly line represents a single machine language instruction (with the exception of some "pseudo-ops" such as ENDP and PROC which are directives to the assembler tool), and has a corresponding binary representation in memory. For example the machine code corresponding to mov eax,11 in the function fn() is:

b8 0b 00 00 00  // Load (move) the literal value 0x0000000B to register EAX

where b8 is the opcode for MOV in hexadecimal (base 16) notation, and 0b 00 00 00 is the operand 11 as a 32-bit little-endian (least significant byte first) integer. Note that hexadecimal is commonly used to present binary data because a single hex digit corresponds to exactly 4 binary digits (bits), and reading long strings of 1's and 0's is impractical. For example b8 0b 00 00 00 in binary, hexadecimal and decimal is:

1011 1000  0000 1011  0000 0000  0000 0000  0000 0000  // Binary
   B 8        0 B        0 0        0 0        0 0     // Hex (nibbles)
   184         11         0          0          0      // Decimal

See https://www.cs.uaf.edu/2016/fall/cs301/lecture/09_28_machinecode.html for additional examples of x86 machine code and corresponding assembly.

As I mentioned at the start, you can use a source-level debugger to directly inspect the relationship between source code and the machine code translation. For example the following is an example in VS Code showing the source (C++ code in this case, but the principle is the same), and the corresponding assembly code and its corresponding machine code in hexadecimal:

enter image description here [source: https://user-images.githubusercontent.com/80216624/115053737-6a850280-9f1a-11eb-8505-a7c5570af71a.gif]

See how the cursor in the source matches the instruction at the cursor in the disassembly. Not that it is not always that straightforward in optimised code, where there is often a less direct correspondence between source and machine code (and also why source-level debugging is normally performed on un-optimised code).

Note also that each instruction has an address. The address 0x00000001400BDF1B in this case is the address of the function func(), so would be the value of a pointer-to-func() (i.e. the value of func).

Another example from Visual Studio 2022 using the same code as the Compiler Explorer example above: enter image description here Note that the code generation differs from that in Compiler Explorer - different compiler version, different compiler settings. Also in this example the generating source is interspersed, with line number references so you can see exactly how each line is translated - noting that this generally only makes sense for unoptimized code use in debugging.

Further reading on how computers work at the lowest possible level: Code The Hidden Language of Computer Hardware and Software

like image 43
Clifford Avatar answered Sep 14 '26 10:09

Clifford



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!