This page covers intricate topics in C, including dynamic memory allocation, a deeper dive into memory representation (endianness, alignment), advanced operators, and the nuances of integer overflow/underflow and floating-point precision.
Allocating memory during program execution (runtime) using functions like malloc(), calloc(), realloc(), and free(). This is crucial for managing data structures whose size isn't known at compile time.
malloc(): Allocates a block of memory of a specified size and returns a pointer to the beginning of the block. The memory is uninitialized.calloc(): Allocates a block of memory for an array of elements and initializes all bytes to zero.realloc(): Changes the size of an already allocated memory block.free(): Deallocates previously allocated memory, returning it to the system. Essential to prevent memory leaks.
#include <stdio.h>
#include <stdlib.h> // For malloc, calloc, realloc, free
int main() {
int *arr;
int n;
printf("Enter the number of elements: ");
scanf("%d", &n);
// Allocate memory for n integers using malloc
arr = (int *) malloc(n * sizeof(int));
// Check if malloc was successful
if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1; // Indicate error
}
printf("Enter %d integers:\n", n);
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
printf("The entered elements are: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Reallocate memory for more elements
int new_n = n + 2;
arr = (int *) realloc(arr, new_n * sizeof(int));
if (arr == NULL) {
printf("Memory reallocation failed!\n");
return 1;
}
printf("Enter %d more integers for new_n:\n", 2);
for (int i = n; i < new_n; i++) {
scanf("%d", &arr[i]);
}
printf("Elements after realloc: ");
for (int i = 0; i < new_n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Free the allocated memory
free(arr);
arr = NULL; // Good practice to set freed pointer to NULL
return 0;
}
Enter the number of elements: 3
Enter 3 integers:
1 2 3
The entered elements are: 1 2 3
Enter 2 more integers for new_n:
4 5
Elements after realloc: 1 2 3 4 5
(User input is shown on the same line as the prompt in this example.)
How data is physically stored in memory.
int or float) are stored in memory.
#include <stdio.h>
int main() {
unsigned int i = 1;
char *c = (char*)&i; // Point to the first byte of 'i'
if (*c) {
printf("System is Little-Endian\n");
} else {
printf("System is Big-Endian\n");
}
printf("Value of i: %u\n", i);
printf("First byte: %d\n", *c); // Will be 1 for little-endian, 0 for big-endian
return 0;
}
System is Little-Endian
Value of i: 1
First byte: 1
#include <stdio.h>
#include <stddef.h> // For offsetof
// Without explicit packing
struct Example1 {
char c1; // 1 byte
int i; // 4 bytes
char c2; // 1 byte
};
// With members ordered by size (often reduces padding)
struct Example2 {
int i; // 4 bytes
char c1; // 1 byte
char c2; // 1 byte
};
// Using pragma pack (compiler-specific, might not be portable)
// #pragma pack(push, 1) // Set packing to 1 byte
// struct Example3 {
// char c1;
// int i;
// char c2;
// };
// #pragma pack(pop) // Restore default packing
int main() {
printf("Size of Example1: %zu bytes\n", sizeof(struct Example1));
printf("Size of Example2: %zu bytes\n", sizeof(struct Example2));
// printf("Size of Example3 (packed): %zu bytes\n", sizeof(struct Example3)); // Uncomment if using pragma pack
// Output of Example1 will likely be 12 (1 + 3 (padding) + 4 + 1 + 3 (padding))
// Output of Example2 will likely be 8 (4 + 1 + 1 + 2 (padding))
// The actual sizes depend on compiler, architecture, and default alignment.
return 0;
}
Size of Example1: 12 bytes
Size of Example2: 8 bytes
& (AND), | (OR), ^ (XOR), ~ (NOT), << (Left Shift), >> (Right Shift).sizeof operator: Returns the size (in bytes) of a variable or a data type.
#include <stdio.h>
int main() {
// Bitwise operators
int a = 5; // Binary: 0101
int b = 3; // Binary: 0011
printf("a & b (AND): %d\n", a & b); // 0001 (1)
printf("a | b (OR): %d\n", a | b); // 0111 (7)
printf("a ^ b (XOR): %d\n", a ^ b); // 0110 (6)
printf("~a (NOT) (for 8-bit, 2's complement): %d\n", ~a); // Depends on int size, usually -6 for 32-bit int
printf("a << 1 (Left Shift): %d\n", a << 1); // 1010 (10)
printf("b >> 1 (Right Shift): %d\n", b >> 1); // 0001 (1)
// sizeof operator
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of double: %zu bytes\n", sizeof(double));
printf("Size of a: %zu bytes\n", sizeof(a));
// Type Casting
float f_val = 10.75;
int i_val = (int)f_val; // Cast float to int (truncates decimal part)
printf("Float value: %.2f, Integer value after cast: %d\n", f_val, i_val);
int int_sum = 5;
int int_count = 2;
double average = (double)int_sum / int_count; // Cast one operand to double for float division
printf("Average: %.2f\n", average);
return 0;
}
a & b (AND): 1
a | b (OR): 7
a ^ b (XOR): 6
~a (NOT) (for 8-bit, 2's complement): -6
a << 1 (Left Shift): 10
b >> 1 (Right Shift): 1
Size of int: 4 bytes
Size of double: 8 bytes
Size of a: 4 bytes
Float value: 10.75, Integer value after cast: 10
Average: 2.50
MAX_UINT + 1 becomes 0).
#include <stdio.h>
#include <limits.h> // For INT_MAX, INT_MIN
#include <float.h> // For DBL_EPSILON
#include <math.h> // For fabs
int main() {
// Integer Overflow
int max_int = INT_MAX;
printf("Max int: %d\n", max_int);
printf("Max int + 1: %d\n", max_int + 1); // Undefined behavior for signed int
unsigned int max_unsigned_int = UINT_MAX;
printf("Max unsigned int: %u\n", max_unsigned_int);
printf("Max unsigned int + 1: %u\n", max_unsigned_int + 1); // Wraps around to 0
// Integer Underflow
int min_int = INT_MIN;
printf("Min int: %d\n", min_int);
printf("Min int - 1: %d\n", min_int - 1); // Undefined behavior for signed int
// Floating-point precision
float sum_float = 0.0f;
for (int i = 0; i < 1000000; i++) {
sum_float += 0.000001f; // Adding a small number many times
}
printf("Sum of 1,000,000 * 0.000001 (float): %.10f\n", sum_float); // Might not be exactly 1.0
double sum_double = 0.0;
for (int i = 0; i < 1000000; i++) {
sum_double += 0.000001; // Adding a small number many times
}
printf("Sum of 1,000,000 * 0.000001 (double): %.10lf\n", sum_double); // More accurate, closer to 1.0
// Comparing floating-point numbers (use an epsilon)
double a = 0.1 + 0.2;
double b = 0.3;
if (a == b) { // This comparison might be false due to precision issues
printf("0.1 + 0.2 == 0.3 (direct comparison)\n");
} else {
printf("0.1 + 0.2 != 0.3 (direct comparison)\n");
}
// Correct way to compare floats
if (fabs(a - b) < DBL_EPSILON) {
printf("0.1 + 0.2 == 0.3 (with epsilon comparison)\n");
} else {
printf("0.1 + 0.2 != 0.3 (with epsilon comparison)\n");
}
return 0;
}
max_int + 1 and min_int - 1 are undefined, but often show wraparound)
Max int: 2147483647
Max int + 1: -2147483648
Max unsigned int: 4294967295
Max unsigned int + 1: 0
Min int: -2147483648
Min int - 1: 2147483647
Sum of 1,000,000 * 0.000001 (float): 0.9999999404
Sum of 1,000,000 * 0.000001 (double): 1.0000000000
0.1 + 0.2 != 0.3 (direct comparison)
0.1 + 0.2 == 0.3 (with epsilon comparison)