Understanding C Arrays and Pointers: Size, Memory Allocation, and sizeof Behavior
Three Ways to Define Arrays in C
Array definition in C can be achieved through three distinct approaches, each with different characteristics regarding size determination, memory location, and lifetime.
Fixed-Size Arrays
Array size is determined at compile time:
#include <stdio.h>
int main() {
int data[5] = {10};
printf("Size: %zu bytes\n", sizeof(data));
return 0;
}
Memory resides on the stack. When the enclosing function returns, the array is automatically destroyed. Stack-allocated arrays are suitable for small, predictable data sets.
Variable-Length Arrays (VLA)
C99 introduced VLAs where dimensions are determined at runtime:
#include <stdio.h>
int main() {
int size;
printf("Enter array size: ");
scanf("%d", &size);
int buffer[size];
printf("Array occupies %zu bytes\n", sizeof(buffer));
return 0;
}
VLAs remain on the stack, meaning available space is typically limited compared to heap memory. When the function exits, VLA storage is reclaimed automatically.
C11 Note: Variable-length arrays became an optional feature. Compiler support varies—GCC enables them by default while Clang may restrict or disable them. Portability concerns exist when relying on VLAs.
Dynamic Memory Allocation
Using malloc for runtime-sized arrays:
#include <stdio.h>
#include <stdlib.h>
int main() {
int count = 8;
int *heapArray = (int*)malloc(count * sizeof(int));
if (heapArray == NULL) {
return 1;
}
printf("Pointer size: %zu bytes\n", sizeof(heapArray));
free(heapArray);
return 0;
}
Memory comes from the heap, which offers substantially more capacity than the stack. This allocation persists until explicitly released via free(). The programmer bears full responssibility for deallocation—failing to release memory results in leaks that accumulate over time.
Memory Leak: When progarms allocate memory via
malloc,calloc, orreallocwithout correspondingfreecalls, the memory remains reserved even after the data becomes unnecessary. These orphaned blocks gradually consume available memory, degrading performance and potentially causing crashes.
The sizeof Trap with Dynamic Arrays
A critical misconception involves using sizeof on dynamically allocated arrays:
int *ptr = (int*)malloc(10 * sizeof(int));
int len = sizeof(ptr) / sizeof(int); // WRONG: returns pointer size, not element count
The variable ptr is a pointer, not an array. sizeof(ptr) yields the pointer's size (typically 8 bytes on 64-bit systems), not the allocated buffer's size. The division 8 / 4 incorrectly produces 2 instead of 10.
Why sizeof Works on VLAs but Not on malloc Pointers
For static and variable-length arrays, the compiler knows the exact dimensions. The array name acts like a constant pointer to the first element, but crucially, the compiler retains size information. Thus sizeof(array) returns the complete array size.
For dynamically allocated memory, malloc returns a raw pointer. The compiler has no knowledge of the allocated extent—only the address. Consequently, sizeof on a malloc'd pointer reports the pointer itself, not the buffer it references.
#include <stdio.h>
int main() {
int vla[6];
int *dynamic = (int*)malloc(6 * sizeof(int));
printf("VLA sizeof: %zu (full array)\n", sizeof(vla));
printf("malloc pointer sizeof: %zu (pointer only)\n", sizeof(dynamic));
free(dynamic);
return 0;
}
Practical consequence: When using dynamic allocation, you must track length separately or use structures that embed size metadata.
Finding Prime Numbers: A Practical Example
The following problem—adapted from Google's recruitment challenges—demonstrates array manipulation and prime checking:
Problem: Given an integer N and digit count K, find the first K-digit prime number appearing consecutively within N. Output "404" if none exists.
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
bool checkPrime(int n) {
if (n < 2) return false;
for (int i = 2; i <= (int)sqrt(n); i++) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int totalDigits, digitCount, digit;
int digits[1000];
scanf("%d %d", &totalDigits, &digitCount);
for (int i = 0; i < totalDigits; i++) {
scanf("%1d", &digit);
digits[i] = digit;
}
int endPosition = totalDigits - digitCount;
for (int start = 0; start <= endPosition; start++) {
int value = 0;
int exponent = digitCount - 1;
for (int offset = 0; offset < digitCount; offset++) {
value += digits[start + offset] * (int)pow(10, exponent);
exponent--;
}
if (checkPrime(value)) {
printf("%0*d\n", digitCount, value);
return 0;
}
}
printf("404\n");
return 0;
}
Key technique: %0*d uses * as a dynamic width specifier. The 0 flag pads with zeros, * substitutes the first argument for width, and d specifies integer output. This ensures K-digit numbers display correctly even when leading digit are zero.