CoreTechED

Loading

Applications of Hashing in C: Complete Guide

Explore hashing applications in C: password storage, file checksums, dictionaries, Bloom filters, load balancing, and symbol tables with code examples.

Applications of Hashing in C: Complete Guide

Applications of Hashing in C: A Comprehensive Tutorial

Hashing is a technique that converts data of any size into a fixed-size value, known as a hash, which can be used for quick data retrieval and comparison. With average O(1) insert, search, and delete, hashing powers some of the most critical systems in computing — from securing passwords to routing internet traffic.

This tutorial explores hashing’s major applications in C: data integrity and security, database and search optimization, network and system infrastructure, and specialized processing. Each application is illustrated with practical C code.


Conceptual Introduction & Fundamentals

What is Hashing?

Hashing maps arbitrary-sized input (a key) to a fixed-size value (a hash) via a hash function. The hash serves as an index into a hash table, enabling O(1) average lookups.

Why Applications Matter

  • Ubiquity: Hashing powers databases, caches, security, networks.
  • Performance: Replaces O(n) or O(log n) operations with O(1) average.
  • Versatility: Applies to strings, integers, files, images.
  • Real-world impact: Every secure login, CDN, and compiler uses hashing.

Categories of Applications

CategoryPrimary Use
Data Integrity & SecurityPassword storage, checksums, blockchain
Database & SearchIndexing, dictionaries, Rabin-Karp
Network & SystemsLoad balancing, Bloom filters, routing
SpecializedImage hashing, symbol tables, graphics

Syntax & Core Mechanics

Application 1: Data Integrity & Security

1.1 Password Storage

Store non-reversible hash instead of plaintext.

c

#include <stdio.h> #include <string.h> #include <stdint.h> /* FNV-1a hash (for educational demo only; use bcrypt/scrypt/argon2 in production) */ uint64_t fnv1a(const char *str) { uint64_t hash = 1469598103934665603ULL; while (*str) { hash ^= (unsigned char)*str++; hash *= 1099511628211ULL; } return hash; } int main(void) { const char *password = "MySecret123"; uint64_t stored = fnv1a(password); printf("Stored hash: 0x%016llx\n", (unsigned long long)stored); /* Verify */ const char *attempt = "MySecret123"; if (fnv1a(attempt) == stored) { printf("Login successful\n"); } else { printf("Login failed\n"); } return 0; }

Expected output:

text

Stored hash: 0x... Login successful

Production note: Use bcrypt, scrypt, or Argon2 with salt for real password storage — FNV is not cryptographically secure.

1.2 File Comparison (Checksum)

c

#include <stdio.h> #include <stdint.h> uint64_t fileHash(const char *path) { FILE *fp = fopen(path, "rb"); if (fp == NULL) return 0; uint64_t hash = 1469598103934665603ULL; int c; while ((c = fgetc(fp)) != EOF) { hash ^= (unsigned char)c; hash *= 1099511628211ULL; } fclose(fp); return hash; } int main(void) { /* Create test file first */ FILE *fp = fopen("test.txt", "w"); if (fp != NULL) { fprintf(fp, "Hello, World!\n"); fclose(fp); } uint64_t h1 = fileHash("test.txt"); printf("File hash: 0x%016llx\n", (unsigned long long)h1); return 0; }

Expected output:

text

File hash: 0x...

Use case: Detect corruption; any single-bit change alters the hash.

1.3 Rabin-Karp String Search

Hash windows of text for fast substring search.

c

#include <stdio.h> #include <string.h> #define BASE 256 #define MOD 1000000007 long long hashString(const char *str, int len) { long long h = 0; for (int i = 0; i < len; i++) { h = (h * BASE + (unsigned char)str[i]) % MOD; } return h; } int rabinKarp(const char *text, const char *pattern) { int n = (int)strlen(text); int m = (int)strlen(pattern); if (m > n) return -1; long long patHash = hashString(pattern, m); long long textHash = hashString(text, m); /* Precompute BASE^(m-1) % MOD */ long long h = 1; for (int i = 0; i < m - 1; i++) h = (h * BASE) % MOD; for (int i = 0; i <= n - m; i++) { if (patHash == textHash) { if (strncmp(text + i, pattern, m) == 0) return i; } if (i < n - m) { textHash = (textHash - (unsigned char)text[i] * h) % MOD; if (textHash < 0) textHash += MOD; textHash = (textHash * BASE + (unsigned char)text[i + m]) % MOD; } } return -1; } int main(void) { const char *text = "The quick brown fox jumps over the lazy dog"; const char *pattern = "brown"; int idx = rabinKarp(text, pattern); if (idx >= 0) printf("Found \"%s\" at index %d\n", pattern, idx); else printf("Not found\n"); return 0; }

Expected output:

text

Found "brown" at index 10

Application 2: Database & Search Optimization

2.1 Dictionary / Associative Array

c

#include <stdio.h> #include <stdlib.h> #include <string.h> #define TABLE_SIZE 101 #define KEY_LEN 32 typedef struct Entry { char key[KEY_LEN]; int value; struct Entry *next; } Entry; typedef struct { Entry *buckets[TABLE_SIZE]; } Dict; static unsigned int hashString(const char *s) { unsigned int h = 5381; while (*s) h = ((h << 5) + h) + (unsigned char)*s++; return h % TABLE_SIZE; } void dictPut(Dict *d, const char *key, int value) { unsigned int idx = hashString(key); Entry *cur = d->buckets[idx]; while (cur) { if (strcmp(cur->key, key) == 0) { cur->value = value; return; } cur = cur->next; } Entry *e = malloc(sizeof(Entry)); if (e == NULL) return; strncpy(e->key, key, KEY_LEN - 1); e->key[KEY_LEN - 1] = '\0'; e->value = value; e->next = d->buckets[idx]; d->buckets[idx] = e; } int dictGet(Dict *d, const char *key, int *out) { unsigned int idx = hashString(key); Entry *cur = d->buckets[idx]; while (cur) { if (strcmp(cur->key, key) == 0) { *out = cur->value; return 1; } cur = cur->next; } return 0; } void dictFree(Dict *d) { for (int i = 0; i < TABLE_SIZE; i++) { Entry *cur = d->buckets[i]; while (cur) { Entry *next = cur->next; free(cur); cur = next; } } } int main(void) { Dict d = {0}; dictPut(&d, "apple", 10); dictPut(&d, "banana", 20); dictPut(&d, "cherry", 30); int v; if (dictGet(&d, "banana", &v)) printf("banana → %d\n", v); if (dictGet(&d, "grape", &v)) printf("grape → %d\n", v); else printf("grape not found\n"); dictFree(&d); return 0; }

Expected output:

text

banana → 20 grape not found

2.2 Frequency Counting

c

#include <stdio.h> #include <stdlib.h> void countFrequency(const int arr[], int n, int maxValue) { int *freq = calloc(maxValue + 1, sizeof(int)); if (freq == NULL) return; for (int i = 0; i < n; i++) freq[arr[i]]++; for (int i = 0; i <= maxValue; i++) { if (freq[i] > 0) printf(" %d → %d\n", i, freq[i]); } free(freq); } int main(void) { int arr[] = {1, 3, 5, 3, 2, 1, 5, 3}; int n = sizeof(arr) / sizeof(arr[0]); printf("Frequencies:\n"); countFrequency(arr, n, 5); return 0; }

Expected output:

text

Frequencies: 1 → 2 2 → 1 3 → 3 5 → 2

Application 3: Network & System Infrastructure

3.1 Bloom Filter (Membership Test)

c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define BLOOM_SIZE 1024 /* bits */ #define NUM_HASHES 3 typedef struct { uint8_t bits[BLOOM_SIZE / 8]; } Bloom; static void bloomSet(Bloom *b, int bit) { b->bits[bit / 8] |= (uint8_t)(1u << (bit % 8)); } static int bloomGet(const Bloom *b, int bit) { return (b->bits[bit / 8] >> (bit % 8)) & 1u; } static unsigned int hash1(const char *s) { unsigned int h = 5381; while (*s) h = ((h << 5) + h) + (unsigned char)*s++; return h % BLOOM_SIZE; } static unsigned int hash2(const char *s) { unsigned int h = 0; while (*s) h = (h * 31) + (unsigned char)*s++; return h % BLOOM_SIZE; } static unsigned int hash3(const char *s) { unsigned int h = 7; while (*s) h = (h * 131) + (unsigned char)*s++; return h % BLOOM_SIZE; } void bloomAdd(Bloom *b, const char *key) { bloomSet(b, hash1(key)); bloomSet(b, hash2(key)); bloomSet(b, hash3(key)); } int bloomContains(const Bloom *b, const char *key) { return bloomGet(b, hash1(key)) && bloomGet(b, hash2(key)) && bloomGet(b, hash3(key)); } int main(void) { Bloom b = {{0}}; bloomAdd(&b, "apple"); bloomAdd(&b, "banana"); bloomAdd(&b, "cherry"); printf("apple: %s\n", bloomContains(&b, "apple") ? "maybe" : "no"); printf("banana: %s\n", bloomContains(&b, "banana") ? "maybe" : "no"); printf("grape: %s\n", bloomContains(&b, "grape") ? "maybe" : "no"); return 0; }

Expected output (typical):

text

apple: maybe banana: maybe grape: no

Key insight: Bloom filters never produce false negatives, only false positives.

3.2 Load Balancing (Consistent Hashing Skeleton)

c

#include <stdio.h> #include <string.h> #define NUM_SERVERS 4 const char *servers[NUM_SERVERS] = { "server-1", "server-2", "server-3", "server-4" }; unsigned int hashString(const char *s) { unsigned int h = 5381; while (*s) h = ((h << 5) + h) + (unsigned char)*s++; return h; } const char *routeRequest(const char *key) { unsigned int idx = hashString(key) % NUM_SERVERS; return servers[idx]; } int main(void) { const char *requests[] = {"user42", "session7", "cart9", "user7"}; int n = sizeof(requests) / sizeof(requests[0]); for (int i = 0; i < n; i++) { printf("%-10s → %s\n", requests[i], routeRequest(requests[i])); } return 0; }

Expected output (typical):

text

user42 → server-3 session7 → server-2 cart9 → server-1 user7 → server-4

Application 4: Specialized Processing

4.1 Symbol Table (Compiler Use Case)

Maps identifiers to metadata.

c

#include <stdio.h> #include <stdlib.h> #include <string.h> #define TABLE_SIZE 101 #define NAME_LEN 32 typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_CHAR } VarType; typedef struct Symbol { char name[NAME_LEN]; VarType type; int scope; struct Symbol *next; } Symbol; typedef struct { Symbol *buckets[TABLE_SIZE]; } SymbolTable; static unsigned int hashName(const char *s) { unsigned int h = 5381; while (*s) h = ((h << 5) + h) + (unsigned char)*s++; return h % TABLE_SIZE; } void symAdd(SymbolTable *st, const char *name, VarType type, int scope) { unsigned int idx = hashName(name); Symbol *s = malloc(sizeof(Symbol)); if (s == NULL) return; strncpy(s->name, name, NAME_LEN - 1); s->name[NAME_LEN - 1] = '\0'; s->type = type; s->scope = scope; s->next = st->buckets[idx]; st->buckets[idx] = s; } Symbol *symFind(SymbolTable *st, const char *name) { unsigned int idx = hashName(name); Symbol *s = st->buckets[idx]; while (s) { if (strcmp(s->name, name) == 0) return s; s = s->next; } return NULL; } void symFree(SymbolTable *st) { for (int i = 0; i < TABLE_SIZE; i++) { Symbol *s = st->buckets[i]; while (s) { Symbol *next = s->next; free(s); s = next; } } } const char *typeName(VarType t) { switch (t) { case TYPE_INT: return "int"; case TYPE_FLOAT: return "float"; case TYPE_CHAR: return "char"; } return "unknown"; } int main(void) { SymbolTable st = {0}; symAdd(&st, "count", TYPE_INT, 0); symAdd(&st, "price", TYPE_FLOAT, 0); symAdd(&st, "initial", TYPE_CHAR, 1); Symbol *s = symFind(&st, "price"); if (s) printf("%s : %s (scope %d)\n", s->name, typeName(s->type), s->scope); symFree(&st); return 0; }

Expected output:

text

price : float (scope 0)

Full Practical Code Example with Explanation

This program combines multiple hashing applications in a single file-integrity and password manager demo.

c

/** * hashing_applications.c * Demonstrates hashing applications: password storage, * file checksums, frequency counting, and Bloom filters. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <errno.h> #define MAX_LINE_LEN 100 #define BLOOM_SIZE 1024 #define NUM_HASHES 3 #define FNV_OFFSET 1469598103934665603ULL #define FNV_PRIME 1099511628211ULL /* FNV-1a hash */ uint64_t fnv1a(const char *str) { uint64_t h = FNV_OFFSET; while (*str) { h ^= (unsigned char)*str++; h *= FNV_PRIME; } return h; } /* Password demo */ void demoPassword(void) { printf("\n=== Password Storage ===\n"); const char *password = "MySecret123"; uint64_t stored = fnv1a(password); printf("Stored hash: 0x%016llx\n", (unsigned long long)stored); const char *attempts[] = {"MySecret123", "wrongpass", "MySecret124"}; for (int i = 0; i < 3; i++) { uint64_t h = fnv1a(attempts[i]); printf(" Attempt \"%s\": %s\n", attempts[i], (h == stored) ? "SUCCESS" : "FAIL"); } } /* File checksum demo */ uint64_t fileHash(const char *path) { FILE *fp = fopen(path, "rb"); if (fp == NULL) return 0; uint64_t h = FNV_OFFSET; int c; while ((c = fgetc(fp)) != EOF) { h ^= (unsigned char)c; h *= FNV_PRIME; } fclose(fp); return h; } void demoFileHash(void) { printf("\n=== File Checksum ===\n"); /* Create test file */ FILE *fp = fopen("demo.txt", "w"); if (fp == NULL) { perror("fopen"); return; } fprintf(fp, "Hello, hashing!\n"); fclose(fp); uint64_t h = fileHash("demo.txt"); printf("Hash of demo.txt: 0x%016llx\n", (unsigned long long)h); /* Modify file */ fp = fopen("demo.txt", "a"); if (fp == NULL) return; fprintf(fp, "Extra line.\n"); fclose(fp); uint64_t h2 = fileHash("demo.txt"); printf("After modification: 0x%016llx\n", (unsigned long long)h2); printf("Files match: %s\n", (h == h2) ? "YES" : "NO"); } /* Frequency counting */ void demoFrequency(void) { printf("\n=== Frequency Counting ===\n"); int arr[] = {1, 3, 5, 3, 2, 1, 5, 3}; int n = sizeof(arr) / sizeof(arr[0]); int maxV = 5; int *freq = calloc(maxV + 1, sizeof(int)); if (freq == NULL) return; for (int i = 0; i < n; i++) freq[arr[i]]++; for (int i = 0; i <= maxV; i++) { if (freq[i]) printf(" %d → %d\n", i, freq[i]); } free(freq); } /* Bloom filter */ typedef struct { uint8_t bits[BLOOM_SIZE / 8]; } Bloom; static unsigned int h1(const char *s) { unsigned int h = 5381; while (*s) h = ((h << 5) + h) + (unsigned char)*s++; return h % BLOOM_SIZE; } static unsigned int h2(const char *s) { unsigned int h = 0; while (*s) h = (h * 31) + (unsigned char)*s++; return h % BLOOM_SIZE; } static unsigned int h3(const char *s) { unsigned int h = 7; while (*s) h = (h * 131) + (unsigned char)*s++; return h % BLOOM_SIZE; } static void bloomSet(Bloom *b, int bit) { b->bits[bit / 8] |= (uint8_t)(1u << (bit % 8)); } static int bloomGet(const Bloom *b, int bit) { return (b->bits[bit / 8] >> (bit % 8)) & 1u; } void bloomAdd(Bloom *b, const char *k) { bloomSet(b, h1(k)); bloomSet(b, h2(k)); bloomSet(b, h3(k)); } int bloomHas(const Bloom *b, const char *k) { return bloomGet(b, h1(k)) && bloomGet(b, h2(k)) && bloomGet(b, h3(k)); } void demoBloom(void) { printf("\n=== Bloom Filter ===\n"); Bloom b = {{0}}; const char *inserted[] = {"apple", "banana", "cherry"}; for (int i = 0; i < 3; i++) bloomAdd(&b, inserted[i]); const char *tests[] = {"apple", "banana", "cherry", "grape", "kiwi"}; for (int i = 0; i < 5; i++) { printf(" %-8s: %s\n", tests[i], bloomHas(&b, tests[i]) ? "maybe" : "no"); } } int main(void) { printf("=== Applications of Hashing ===\n"); demoPassword(); demoFileHash(); demoFrequency(); demoBloom(); printf("\nDone.\n"); return 0; }

Expected Output (sample run)

text

=== Applications of Hashing === === Password Storage === Stored hash: 0x... Attempt "MySecret123": SUCCESS Attempt "wrongpass": FAIL Attempt "MySecret124": FAIL === File Checksum === Hash of demo.txt: 0x... After modification: 0x... Files match: NO === Frequency Counting === 1 → 2 2 → 1 3 → 3 5 → 2 === Bloom Filter === apple : maybe banana : maybe cherry : maybe grape : no kiwi : no Done.

Common Pitfalls, Edge Cases & Memory Considerations

1. Using Weak Hashes for Security

FNV/MD5/SHA-1 are unsuitable for password storage.

Solution: Use bcrypt, scrypt, or Argon2 with unique salts.

2. Bloom Filter False Negatives

Bloom filters must use all hash functions consistently; missing bits cause false negatives.

Solution: Always set/check all hash outputs; never partial.

3. Hash Table Memory Leaks

Forgetting to free bucket lists or arrays causes leaks.

Solution: Walk each bucket and free nodes before freeing the table.

4. Collision Attacks

Adversarial inputs can trigger worst-case collisions in weak hashes.

Solution: Use randomized seeds (SipHash, murmur with seed).

5. Load Factor Overflow

Ignoring load factor degrades to O(n).

Solution: Rehash when load factor > 0.75.

6. Integer Overflow in Hash Functions

Large multiplications overflow 32-bit int.

Solution: Use uint64_t or unsigned int.

7. File Handle Leaks

Not closing files after hashing.

Solution: Always fclose in all paths.

8. Bloom Filter Sizing

Too small a filter yields many false positives.

Solution: Size based on expected n and desired false-positive rate.

9. Unsalted Passwords

Identical passwords produce identical hashes.

Solution: Always use unique per-user salts.

10. Memory Considerations

  • Hash tables: O(n) entries + bucket array.
  • Bloom filters: O(m) bits, independent of n.
  • Frequency arrays: O(max value) space.
  • Free all dynamic allocations.

Summary & Best Practices

Hashing is one of the most versatile techniques in computing. From securing passwords to routing traffic and deduplicating images, its O(1) average operations enable modern software at scale.

Key Takeaways

  • Hashing powers security, databases, networks, and specialized systems.
  • Password storage must use salted, adaptive hashes (bcrypt/Argon2).
  • Bloom filters trade a small error rate for massive memory savings.
  • Consistent hashing enables stable load balancing.
  • Rabin-Karp uses rolling hashes for fast substring search.
  • Symbol tables map identifiers to metadata during compilation.

Best Practices

  1. Never store plaintext passwords; use salted adaptive hashes.
  2. Use cryptographic hashes (SHA-256) for integrity, not FNV.
  3. Choose table sizes as primes for even distribution.
  4. Rehash when load factor exceeds 0.75.
  5. Free all hash table nodes on cleanup.
  6. Use uint64_t for hash arithmetic to avoid overflow.
  7. Size Bloom filters based on expected elements and target false-positive rate.
  8. Use randomized seeds to prevent collision attacks.
  9. Close file handles after reading.
  10. Profile for large datasets.

Hashing’s blend of speed, simplicity, and versatility makes it an essential tool for every systems programmer.

Leave a Reply

Your email address will not be published. Required fields are marked *