Titas Dey

Well the title is a clickbait,but it’s true in a limited sense.

If you've written some C code you've probably used most of the features in C like structure,functions,pointers,arrays and perhaps even the preprocessor.

However, I will talk about one of the lesser used features in C – union


The Union

A union allocates a single shared block of memory, large enough to hold its largest member (with some padding, depending on alignment). Unlike a struct, which allocates distinct memory for each member, a union allows multiple members to occupy the same memory space.

For example:

#include<stdio.h>
#include<string.h>

struct xyz {
    int x;
    float y;
    char z[10];
};

union tuv {
    int t;
    float u;
    char v[10];
};

int main(void) {
    struct xyz st_eg;
    union tuv un_eg;

    printf("%d\n", sizeof(st_eg)); // O/P: 20 bytes (4 + 4 + 10 + 2 bytes padding)
    printf("%d\n", sizeof(un_eg)); // O/P: 12 bytes (10 bytes for v + 2 bytes padding)

    strcpy(&un_eg.v, "HelloWorld");

    printf("%s\n", un_eg.v);  // O/P: HelloWorld 
    printf("%f\n", un_eg.u);  // O/P: 1143139122437582505939828736.000000

    return 0;
}

Here, both the integer, float, and character array occupy the same memory region. When "HelloWorld" is copied into the character array v, reading that memory as a float outputs the string "HelloWorld" typecasted into float a short essay on union.


  • But why do we need union ?
  • Why to allocate memory for only the largest member and not all of them using struct ?

A union is valuable when you want different interpretations of the same memory.


Example 1: Storing an IPv4 Address

#include<stdio.h>

typedef union {
    unsigned int ip_add;
    unsigned char bytes[4];
} ipv4_add;

int main(void) {
    ipv4_add my_address = {0};

    my_address.bytes[0] = 127;
    my_address.bytes[1] = 55;
    my_address.bytes[2] = 115;
    my_address.bytes[3] = 0;

    printf("%x\n", my_address.ip_add); // O/P: 73377f
    return 0;
}

Explanation

Using a union, we can store both the integer representation and the byte-wise representation of an IPv4 address within the same space. This approach eliminates the need for explicit bit-shifting or manual conversions.


Example 2: Unions in Embedded Programming

Unions are widely used in embedded systems to represent hardware registers that can be accessed both as a whole and as individual fields.

#include<stdio.h>

union HWRegister {
    struct { // annonymous structure
        unsigned char parity;
        unsigned char control;
        unsigned char stopbits;
        unsigned char direction;
    };
    unsigned int reg;
};

int main(void) {
    union HWRegister gpioa;

    gpioa.reg = 0x14424423;
    printf("%x\n", gpioa.stopbits); // O/P: 14

    return 0;
}

In this example, the same memory can be accessed as a single 32-bit register or through specific bit fields. This design improves clarity while maintaining memory efficiency — a common requirement in low-level programming.


Example 3: A Glimpse of Polymorphism in C

Now coming back to the title , we can do something similar to OOP in C:

#include<stdio.h>

typedef enum {
    JSON_STR,
    JSON_BYTE,
    JSON_INT,
} json_type_t;

#define JSON_MAX_STR 64

typedef struct {
   json_type_t type;
   union {
       char str[JSON_MAX_STR];
       char byte;
       int number;
   };
} json_t;

void printJSON(json_t *json) {
    switch (json->type) {
        case JSON_STR:
            printf("%s\n", json->str);
            break;
        case JSON_BYTE:
            printf("%c\n", json->byte);
            break;
        case JSON_INT:
            printf("%d\n", json->number);
            break;
    }
}

int main(void) {
    json_t myJSON;
    myJSON.type = JSON_INT;
    myJSON.number = 97;

    printJSON(&myJSON);
    return 0;
}

Here, the structure json_t can hold one of several possible data types — a string, a single byte, or an integer. The active type is determined at runtime using the type field.

There are some issues in this , in C the types are not tightly enforced by the compiler , so if we do

myJSON.type = JSON_STR;// // instead of JSON_INT
myJSON.number = 97;
printJSON(&myJSON); // O/P: a 
  • The output will be : a (the ascii charector of value 97)

And that's all.

print("Titas , signing out ")

Arrow Function vs Regular Function in JavaScript

Yeah, everyone already knows the syntax is different. No need to waste time on that.

Let’s look at what actually matters — how they behave differently.


1. arguments

Regular functions come with this built-in thing called arguments object. Even if you don’t define any parameters, you can still access whatever got passed when the function was called.

Arrow functions? Nope. No arguments object. Try using it, and it’ll just throw an error.

Regular function:

function test() {
  console.log(arguments);
}

test(1, "hello world", true); 
// o/p
// { '0': 1, '1': 'hello world', '2': true }

Arrow function:

const test = () => {
  console.log(arguments); 
};

test(1, "hello world", true); // Throws ReferenceError

2. return

Arrow functions have implicit return but regular functions don't. i.e We can return the result automatically if we write it in a single line , inside a parenthesis in arrow functions. Regular functions always require the return keyword.

Regular function:

function add(a, b) {
 const c = a + b;
}

console.log(add(5, 10)); // o/p : undefined 

Arrow function:

const add = (a, b) => (a + b);

console.log(add(5, 10)); // o/p : 15

3. this

Arrow functions do not have their own this binding. Instead, they lexically inherit this from the surrounding (parent) scope at the time of definition. This means the value of this inside an arrow function is fixed and cannot be changed using .call(), .apply(), or .bind().

Regular functions, on the other hand, have dynamic this binding — it depends on how the function is invoked. When called as a method, this refers to the object; when called standalone, this can be undefined (in strict mode) or refer to the global object (in non-strict mode).

Because of this behavior, arrow functions are commonly used in cases where you want to preserve the outer this context, such as in callbacks or within class methods that rely on this from the class instance.

Regular function :

const obj = {
  name: "Titas",
  sayHi: function () {
    console.log(this.name);
  }
};

obj.sayHi(); // o/p : Titas

Arrow function :

const obj = {
  name: "Titas",
  sayHi: () => {
    console.log(this.name);
  }
};

obj.sayHi(); // o/p :  undefined

print("Titas signing out !")

The college is really gonna teach us Data Structures and Algorithms in C . And make me write 100 line codes in paper for lab manuals .