"Why is operator (*) needed to access the value of an int* variable and not for char* ?"
Because at the printf() function, the format specifier %s expects a matching argument of type char * - a pointer to a string, while %d expects an argument of type int.
In the latter case, the dereference operator * is required to dereference int_ptr and yield the value of the int object int_ptr points to.
As char_ptr is already of type char* as required by %s, there is no need to dereference it.
Side notes:
1.
int_ptr = (int *) malloc(12);
Note that with 12 Bytes on most modern system you be able to allocate only 1 int object as it requires 8 Byte. The remaining 4 Byte are not sufficient to hold another.
If you want to allocate space for only one int, use sizeof(*int_ptr):
int_ptr = (*int) malloc(sizeof(*int_ptr));
2.
Also don´t forget to free() the storage allocated by malloc() after its use:
free(int_ptr);
free(char_ptr);
3.
Furthermore there is no need to cast the return value of malloc(): Do I cast the result of malloc?
char_ptr = malloc(mem_size);
int_ptr = malloc(sizeof(*int_ptr));