Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is memory managed for non-declared entities in the C language?

Tags:

c

memory

For example: In the following code, how and where is the number '10' used for the comparison stored?

#include<stdio.h>
#include<conio.h>

int main()
{
    int x = 5;
    if (x > 10)
        printf("X is greater than 10");
    else if (x < 10)
       printf("X is lesser than 10");
    else
        printf("x = 10");
    getch();
    return 0;
}

Pardon me for not giving enough details. Instead of initializing 'x' directly with '5', if we scan and get it from the user we know how memory is allocated for 'x'. But how memory is allocated for the literal number '10' which is not stored in any variable?

like image 394
Vivek A Avatar asked Sep 04 '15 09:09

Vivek A


People also ask

How memory is managed in C language?

In C, the library function malloc is used to allocate a block of memory on the heap. The program accesses this block of memory via a pointer that malloc returns. When the memory is no longer needed, the pointer is passed to free which deallocates the memory so that it can be used for other purposes.

What are memory management operators in C Plus Plus?

Memory Management Operators. In C language, we use the malloc() or calloc() functions to allocate the memory dynamically at run time, and free() function is used to deallocate the dynamically allocated memory.

What is static memory allocation in C?

The static memory allocation is a fixed amount of memory that is allocated during the compile time of a program and the stack data structure. There are different types of memory architectures available in C language and memory is allocated in two areas, either in the stack memory area or the heap memory area.

How is memory managed differently between C and Java?

In C, we allocate memory and free it by malloc and free, the memory is allocated and deallocated by the user, while in Java, we use garbage collector that deletes the objects with no references to them.


2 Answers

In your particular code, x is initialized to 5 and is never changed. An optimizing compiler is able to constant fold and propagate that information. So it probably would generate the equivalent of

int main() {
 printf("X is lesser than 10");
 getch();
 return 0;
}

notice that the compiler would also have done dead code elimination.

So both constants 5 and 10 would have disappeared.

BTW, <conio.h> and getch are not in standard C99 or C11. My Linux system don't have them.

In general (and depending upon the target processor's instruction set and the ABI) small constants are often embedded in some single machine code instruction (as an immediate operand), as Kilian answered. Some large constants (e.g. floating point numbers, literal strings, most const global or static arrays and aggregates) might get inserted and compiled as read only data in the code segment (then the constant inside machine register-load instructions would be an address or some offset relative to PC for PIC); see also this. Some architectures (e.g. SPARC, RISC-V, ARM, and other RISC) are able to load a wide constant in a register by two consecutive instructions (loading the constant in two parts), and this impacts the relocation format for the linker (e.g. in object files and executables, often in ELF).

I suggest to ask your compiler to emit assembler code, and have a glance at that assembler code. If using GCC (e.g. on Linux, or with Cygwin or MinGW) try to compile with gcc -Wall -O -fverbose-asm -S ; on my Debian/Linux system if I replace getch by getchar in your code I am getting:

        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "X is lesser than 10"
        .text
        .globl  main
        .type   main, @function
main:
.LFB11:
        .cfi_startproc
        subq    $8, %rsp        #,
        .cfi_def_cfa_offset 16
        movl    $.LC0, %edi     #,
        movl    $0, %eax        #,
        call    printf  #
        movq    stdin(%rip), %rdi       # stdin,
        call    _IO_getc        #
        movl    $0, %eax        #,
        addq    $8, %rsp        #,
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
.LFE11:
        .size   main, .-main
        .ident  "GCC: (Debian 4.9.2-10) 4.9.2"
        .section        .note.GNU-stack,"",@progbits

If you are using a 64 bits Windows system, your architecture is very likely to be x86-64. There are tons of documentation describing the ISA (see answers to this) and the x86 calling conventions (and also the Linux x86-64 ABI; you'll find the equivalent document for Windows).

BTW, you should not really care how such constants are implemented. The semantics of your code should not change, whatever the compiler choose to do for implementing them. So leave the optimizations (and such low level choices) to the compiler (i.e. your implementation of C).

like image 119
Basile Starynkevitch Avatar answered Oct 29 '22 03:10

Basile Starynkevitch


The constant 10 is probably stored as an immediate constant in the opcode stream. Issuing a CMP AX,10, with the constant included in the opcode, is usually both smaller and faster than a CMP AX, [BX], where the comparison value must be loaded from memory.

If the constant is too large to fit into the opcode, the alternative is to store it in memory like a static variable, but if the instruction set allows embedded constants, a good compiler should use it - after all, that addressing mode was presumably added because it has advantages over the others.

like image 40
Kilian Foth Avatar answered Oct 29 '22 01:10

Kilian Foth