Skip to content

ramesaliyev/hello-c

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Compile & Run

Compile

$ gcc name1.c name2.c -Wall -o nameX

for ANSI C;

$ gcc name1.c name2.c -std=c99 -pedantic -Wall -o nameX

Run

$ ./nameX

Memory

Memory Size of Types

Common term C type Number of bits Max unsigned
Byte char 8 255
Word short 16 65535
Integer int 32 ~4 billion
Long Integer long 32 / 64 ~4 billion / ~18 qunitillion
Long Long Integer long long 64 ~18 qunitillion
Float float 32 3.402823466 e+38
Double Float double 64 1.7976931348623158 e+308
Pointer * X 32 / 64 ~4 billion / ~18 qunitillion
  • Pointers are just integers. For 32 bit architectures pointers are 32 bits in size, and are 64 bits in size on 64 bit architectures.

  • When we want signed numbers, we center our ranges AROUND 0. So bytes (chars) can go from -128 to 127, shorts from -32768 to 32767, and so on.

  • By default all of the types are signed (except pointers) UNLESS you put an “unsigned” in front of them. You can also place “signed” in front to explicitly say you want the type to be signed.

  • Float and Double types cannot be signed or unsigned.

  • Memory to a machine is just a big “spreadsheet” of numbers. Imagine it as a spreadsheet with only 1 column and a lot of rows. Every cell can store 8 bits (a byte). If you “merge” rows (2, 4, 8) you can store more values as above. But when you merge rows, the next row number doesn't change. You also could still address the “parts” of the merged cell as bytes or smaller units. In the end pointers are nothing more than a number saying “go to memory row 2943298 and get me the integer (4 bytes) located there” (if it was a pointer to an integer). The pointer itself just stores the PLACE in memory where you find the data. The data itself is what you get when you de-reference a pointer.

  • You can have a pointer to pointers, so de-reference a pointer to pointers to get the place in memory where the actual data is then de-reference that again to get the data itself. Follow the chain of pointers if you want values. Since pointers are numbers, you can do math on them like any other. You can advance through memory just by adding 1, 2, 4 or 8 to your pointer to point to the “next thing along” for example, which is how arrays work.

  • Variables are stored on the stack. This is a special place in memory for such temporary variables and data. It grows and shrinks as needed and has a specific order to store data. Other data is likely on the “heap” and you will explicitly ask it to be there. The heap is basically a “big mess of data” where you likely have more permanent data or the larger bits of data. C provides some basic methods to ask for this storage, where the stack allocation space is generally implicit when you call a function.

  • If you don't initialize a variable with initial value, its value may be random garbage that was there before in memory.

  • Arrays have strict ordering in memory, so you can later on use pointers and simple arithmetic to walk up and down an array to access data.

Memory Allocation

malloc

The name "malloc" stands for memory allocation. The malloc() function reserves a block of memory of the specified number of bytes. And, it returns a pointer of type void which can be casted into pointer of any form.

// syntax
data_type* pointer_variable_name;
pointer_variable_name = (data_type *) malloc(byte-size);

// example
int * pointer;
pointer = (int *) malloc(100 * sizeof(int));

Considering the size of int is 4 bytes, this statement allocates 400 bytes of memory. And, the pointer ptr holds the address of the first byte in the allocated memory.

However, if the space is insufficient, allocation fails and returns a NULL pointer.

calloc

The name "calloc" stands for contiguous allocation. The malloc() function allocates a single block of memory. Whereas, calloc() allocates multiple blocks of memory and initializes them to zero.

// syntax
data_type* pointer_variable_name;
pointer_variable_name = (data_type *) calloc(n, element-size);

// example
int * pointer;
pointer = (int *) calloc(100, sizeof(int));

realloc

realloc attempts to extend your available memory range if sufficient memory is available behind it on the heap. If not then it is equivalent to malloc a block of the new size, memcpy your contents there, free the old block. *

An implementation of realloc() may look something like the following: *

void * realloc(void *ptr, size_t size) {
    // realloc() on a NULL pointer is the same as malloc().
    if (ptr == NULL) return malloc(size);

    size_t oldsize = malloc_getsize(ptr);

    // Are we shrinking an allocation? That's easy.
    if (size < oldsize) {
        malloc_setsize(ptr, size);
        return ptr;
    }

    // Can we grow this allocation in place?
    if (malloc_can_grow(ptr, size)) {
        malloc_setsize(ptr, size);
        return ptr;
    }

    // Create a new allocation, move the data there, and free the old one.
    void * newptr = malloc(size);
    if (newptr == NULL) return NULL;
    memcpy(newptr, ptr, oldsize);
    free(ptr);

    return newptr;
}

Note that I'm calling several functions with names starting with malloc_ here. These functions don't actually exist (to the best of my knowledge) in any implementation; they're intended as placeholders for however the allocator a