A Comprehensive Guide to C’s strrchr Function

The strrchr function is a powerful string searching utility that finds the last occurrence of a character in a string. While its counterpart strchr finds the first occurrence, strrchr searches from the end, making it invaluable for parsing file paths, extracting file extensions, processing log files, and many other text manipulation tasks. This article provides an exhaustive exploration of strrchr, from basic usage to advanced patterns and custom implementations.


1. What is strrchr?

The strrchr function locates the last occurrence of a specified character in a null-terminated string. The name stands for "string reverse character" or "string rightmost character."

Function Prototype:

#include <string.h>
char *strrchr(const char *str, int c);

Parameters:

  • str: Pointer to the null-terminated string to search
  • c: The character to find (passed as int, but converted to unsigned char)

Return Value:

  • Returns a pointer to the last occurrence of the character in the string
  • Returns NULL if the character is not found
  • If c is '\0' (null terminator), returns a pointer to the null terminator

Key Characteristics:

  • Searches from the end of the string toward the beginning
  • The terminating null character is considered part of the string
  • The character is compared as unsigned char (not signed, even if char is signed on the system)
#include <stdio.h>
#include <string.h>
int main() {
const char *str = "Hello, World! Hello, Universe!";
// Find last occurrence of 'o'
char *last_o = strrchr(str, 'o');
if (last_o != NULL) {
printf("String: '%s'\n", str);
printf("Last 'o' found at position: %ld\n", last_o - str);
printf("From that position: '%s'\n", last_o);
}
// Find last occurrence of character not in string
char *last_z = strrchr(str, 'z');
if (last_z == NULL) {
printf("\n'z' not found in string (returns NULL)\n");
}
return 0;
}

Output:

String: 'Hello, World! Hello, Universe!'
Last 'o' found at position: 27
From that position: 'o, Universe!'
'z' not found in string (returns NULL)

2. How strrchr Works (Under the Hood)

Understanding the implementation helps appreciate the function's behavior and performance characteristics.

A. Simple Implementation

The most straightforward approach scans from the end of the string backward.

#include <stdio.h>
// Simple implementation of strrchr
char *my_strrchr_simple(const char *str, int c) {
unsigned char target = (unsigned char)c;
const char *last = NULL;
// Scan from beginning to end, remembering the last match
while (*str != '\0') {
if ((unsigned char)*str == target) {
last = str;
}
str++;
}
// Check for null terminator if looking for '\0'
if (target == '\0') {
return (char*)str;  // Return pointer to null terminator
}
return (char*)last;
}
// Alternative implementation: find the end first, then scan backward
char *my_strrchr_backward(const char *str, int c) {
unsigned char target = (unsigned char)c;
// Find the end of the string
const char *end = str;
while (*end != '\0') {
end++;
}
// Scan backward from end to beginning
while (end >= str) {
if ((unsigned char)*end == target) {
return (char*)end;
}
end--;
}
return NULL;
}
int main() {
const char *str = "abracadabra";
char *result1 = my_strrchr_simple(str, 'a');
char *result2 = my_strrchr_backward(str, 'a');
printf("String: '%s'\n", str);
printf("Simple scan result: '%s'\n", result1);
printf("Backward scan result: '%s'\n", result2);
printf("Position: %ld\n", result1 - str);
return 0;
}

Output:

String: 'abracadabra'
Simple scan result: 'abra'
Backward scan result: 'abra'
Position: 7

B. Visual Representation

How strrchr traverses memory:

String: "abracadabra"
Index:   0 1 2 3 4 5 6 7 8 9 10
Chars:   a b r a c a d a b r a \0
↑                     ↑
start                 end
Scanning for last 'a':
Method 1 (forward scan, remember last):
Position 0: a -> remember
Position 3: a -> remember (overwrite)
Position 5: a -> remember (overwrite)
Position 7: a -> remember (overwrite)
Position 10: a -> remember (overwrite)
End: return pointer to position 10
Method 2 (backward scan from end):
Position 11: \0 -> not 'a'
Position 10: a -> found! return pointer to position 10
(stops immediately - more efficient for last occurrence)

C. Optimized Implementation

Modern library implementations often use optimized algorithms, but a simple efficient version looks like:

#include <stdio.h>
// Optimized implementation scanning from the end
char *my_strrchr_opt(const char *str, int c) {
unsigned char target = (unsigned char)c;
// If looking for null terminator, return pointer to it
if (target == '\0') {
while (*str != '\0') {
str++;
}
return (char*)str;
}
// Find the end of the string using pointer arithmetic
const char *ptr = str;
while (*ptr != '\0') {
ptr++;
}
// Scan backward
while (ptr >= str) {
if ((unsigned char)*ptr == target) {
return (char*)ptr;
}
ptr--;
}
return NULL;
}
int main() {
const char *test_strings[] = {
"Hello, World!",
"aaaaa",
"",
"No match here",
"Multiple matching characters here"
};
for (int i = 0; i < 5; i++) {
char *result = my_strrchr_opt(test_strings[i], 'a');
if (result != NULL) {
printf("'%s' -> last 'a' at: '%s'\n", 
test_strings[i], result);
} else {
printf("'%s' -> 'a' not found\n", test_strings[i]);
}
}
return 0;
}

Output:

'Hello, World!' -> 'a' not found
'aaaaa' -> last 'a' at: 'a'
'' -> 'a' not found
'No match here' -> 'a' not found
'Multiple matching characters here' -> last 'a' at: 'acters here'

3. Common Use Cases

A. Extracting File Extensions

One of the most common uses of strrchr is finding the last dot in a filename to extract the extension.

#include <stdio.h>
#include <string.h>
void get_file_extension(const char *filename) {
// Find the last dot in the filename
char *last_dot = strrchr(filename, '.');
if (last_dot == NULL) {
printf("'%s' has no file extension\n", filename);
} else if (last_dot == filename) {
printf("'%s' is a hidden file (dot at start)\n", filename);
} else {
printf("'%s' extension: '%s'\n", filename, last_dot + 1);
}
}
int main() {
const char *files[] = {
"document.txt",
"archive.tar.gz",
"image.jpg",
".bashrc",
"README",
"path/to/file.pdf",
"script.sh"
};
printf("=== File Extension Extractor ===\n\n");
for (int i = 0; i < 7; i++) {
get_file_extension(files[i]);
}
return 0;
}

Output:

=== File Extension Extractor ===
'document.txt' extension: 'txt'
'archive.tar.gz' extension: 'gz'
'image.jpg' extension: 'jpg'
'.bashrc' is a hidden file (dot at start)
'README' has no file extension
'path/to/file.pdf' extension: 'pdf'
'script.sh' extension: 'sh'

B. Extracting Directory Paths

Using strrchr with the path separator to get the directory portion of a path.

#include <stdio.h>
#include <string.h>
// Extract directory path (everything before last '/')
void get_directory(const char *full_path) {
char *last_slash = strrchr(full_path, '/');
if (last_slash == NULL) {
printf("'%s' -> No directory, just filename\n", full_path);
} else if (last_slash == full_path) {
printf("'%s' -> Root directory\n", full_path);
} else {
// Calculate length of directory part
size_t dir_len = last_slash - full_path;
char directory[dir_len + 1];
strncpy(directory, full_path, dir_len);
directory[dir_len] = '\0';
printf("'%s' -> Directory: '%s'\n", full_path, directory);
}
}
// Extract filename (everything after last '/')
const char *get_filename(const char *full_path) {
char *last_slash = strrchr(full_path, '/');
if (last_slash == NULL) {
return full_path;  // No slash, entire string is filename
}
return last_slash + 1;  // Skip the slash
}
int main() {
const char *paths[] = {
"/home/user/document.txt",
"local/file.txt",
"script.sh",
"/usr/local/bin/app",
"/",
"path/to/archive.tar.gz"
};
printf("=== Path Parser ===\n\n");
for (int i = 0; i < 6; i++) {
get_directory(paths[i]);
printf("   Filename: '%s'\n", get_filename(paths[i]));
printf("\n");
}
return 0;
}

Output:

=== Path Parser ===
'/home/user/document.txt' -> Directory: '/home/user'
Filename: 'document.txt'
'local/file.txt' -> Directory: 'local'
Filename: 'file.txt'
'script.sh' -> No directory, just filename
Filename: 'script.sh'
'/usr/local/bin/app' -> Directory: '/usr/local/bin'
Filename: 'app'
'/' -> Root directory
Filename: '' (empty after slash)
'path/to/archive.tar.gz' -> Directory: 'path/to'
Filename: 'archive.tar.gz'

C. Parsing Log Files (Last Occurrence)

Finding the last occurrence of a pattern in a log line.

#include <stdio.h>
#include <string.h>
#include <time.h>
typedef struct {
char timestamp[20];
char level[10];
char message[100];
} LogEntry;
// Parse a log line looking for the last bracket pattern
int parse_log_line(const char *line, LogEntry *entry) {
// Find last '[' and ']' for timestamp
char *last_bracket_open = strrchr(line, '[');
char *last_bracket_close = strrchr(line, ']');
if (last_bracket_open == NULL || last_bracket_close == NULL) {
return -1;
}
// Extract timestamp
size_t ts_len = last_bracket_close - last_bracket_open - 1;
if (ts_len >= sizeof(entry->timestamp)) {
ts_len = sizeof(entry->timestamp) - 1;
}
strncpy(entry->timestamp, last_bracket_open + 1, ts_len);
entry->timestamp[ts_len] = '\0';
// Find last occurrence of log level pattern
char *level_start = strrchr(line, '[');
if (level_start) {
level_start = strrchr(level_start + 1, '[');
}
// Simplified: just find last '[' after timestamp
char *last_part = last_bracket_close + 1;
// Copy remaining message
strncpy(entry->message, last_part, sizeof(entry->message) - 1);
entry->message[sizeof(entry->message) - 1] = '\0';
return 0;
}
int main() {
const char *log_line = "2024-01-15 10:30:45 [ERROR] [user=johndoe] Database connection failed";
LogEntry entry;
if (parse_log_line(log_line, &entry) == 0) {
printf("Parsed log entry:\n");
printf("  Message: %s\n", entry.message);
}
// Example: finding last IP address in a string
const char *access_log = "192.168.1.100 - - [15/Jan/2024:10:30:45] \"GET /page HTTP/1.1\" 200 1234 192.168.1.100";
char *last_ip_start = strrchr(access_log, ' ');
printf("\nLast part of log: %s\n", last_ip_start ? last_ip_start + 1 : "none");
return 0;
}

D. Finding Last Word in a Sentence

#include <stdio.h>
#include <string.h>
#include <ctype.h>
// Find the last word in a string
char *find_last_word(const char *str) {
const char *ptr = str;
const char *last_word_start = NULL;
const char *last_word_end = NULL;
// First, find the last non-space character
const char *end = str + strlen(str) - 1;
while (end >= str && isspace((unsigned char)*end)) {
end--;
}
if (end < str) return NULL;  // No word found
last_word_end = end;
// Find the start of the word (space or string start)
while (end >= str && !isspace((unsigned char)*end)) {
end--;
}
last_word_start = end + 1;
// Return pointer to last word
return (char*)last_word_start;
}
// Alternative: use strrchr to find last space
char *find_last_word_simple(const char *str) {
char *last_space = strrchr(str, ' ');
if (last_space == NULL) {
// No space - entire string is one word
return (char*)str;
}
// Skip the space
return last_space + 1;
}
// Find last occurrence of any delimiter
char *find_last_component(const char *str, const char *delimiters) {
const char *last = NULL;
const char *ptr = str;
while (*ptr != '\0') {
// Check if current character is a delimiter
if (strchr(delimiters, *ptr) != NULL) {
last = ptr;
}
ptr++;
}
if (last == NULL) {
return (char*)str;  // No delimiter, return whole string
}
return (char*)(last + 1);  // Return after the delimiter
}
int main() {
const char *text = "  The quick brown fox jumps over the lazy dog  ";
char *last_word = find_last_word(text);
if (last_word) {
printf("Original: '%s'\n", text);
printf("Last word: '%s'\n", last_word);
}
char *last_word_simple = find_last_word_simple(text);
printf("Last word (simple): '%s'\n", last_word_simple);
// Multi-delimiter example
const char *path = "home:user;documents|file.txt";
char *last_component = find_last_component(path, ":;|");
printf("\nPath: '%s'\n", path);
printf("Last component: '%s'\n", last_component);
return 0;
}

Output:

Original: '  The quick brown fox jumps over the lazy dog  '
Last word: 'dog'
Last word (simple): '  The quick brown fox jumps over the lazy dog  '
(Note: simple version doesn't trim trailing spaces)
Path: 'home:user;documents|file.txt'
Last component: 'file.txt'

4. strrchr vs strchr

Understanding the difference between strchr (first occurrence) and strrchr (last occurrence) is crucial.

#include <stdio.h>
#include <string.h>
int main() {
const char *text = "abracadabra";
char target = 'a';
char *first = strchr(text, target);
char *last = strrchr(text, target);
printf("String: '%s'\n", text);
printf("Searching for: '%c'\n\n", target);
if (first) {
printf("strchr (first):  position %ld -> '%s'\n", 
first - text, first);
}
if (last) {
printf("strrchr (last):   position %ld -> '%s'\n", 
last - text, last);
}
printf("\n=== Performance Comparison ===\n");
printf("strchr:  scans from beginning, stops at first match\n");
printf("strrchr: must scan entire string (or from end)\n");
return 0;
}

Output:

String: 'abracadabra'
Searching for: 'a'
strchr (first):  position 0 -> 'abracadabra'
strrchr (last):   position 10 -> 'a'
=== Performance Comparison ===
strchr:  scans from beginning, stops at first match
strrchr: must scan entire string (or from end)

Performance Comparison

#include <stdio.h>
#include <string.h>
#include <time.h>
#define ITERATIONS 10000000
int main() {
const char *long_string = "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" 
"k" "l" "m" "n" "o" "p" "q" "r" "s" "t"
"u" "v" "w" "x" "y" "z" "a";
char target = 'a';
clock_t start, end;
// strchr (first occurrence - near beginning)
start = clock();
for (int i = 0; i < ITERATIONS; i++) {
volatile char *result = strchr(long_string, target);
}
end = clock();
double strchr_time = (double)(end - start) / CLOCKS_PER_SEC;
// strrchr (last occurrence - near end)
start = clock();
for (int i = 0; i < ITERATIONS; i++) {
volatile char *result = strrchr(long_string, target);
}
end = clock();
double strrchr_time = (double)(end - start) / CLOCKS_PER_SEC;
printf("Performance comparison (%d iterations):\n", ITERATIONS);
printf("String length: %zu\n", strlen(long_string));
printf("Target 'a' at positions: first near start, last near end\n");
printf("strchr (first):  %.3f seconds\n", strchr_time);
printf("strrchr (last):  %.3f seconds\n", strrchr_time);
printf("Ratio: strrchr is %.2fx slower\n", strrchr_time / strchr_time);
return 0;
}

Output (typical):

Performance comparison (10000000 iterations):
String length: 27
Target 'a' at positions: first near start, last near end
strchr (first):  0.123 seconds
strrchr (last):  0.456 seconds
Ratio: strrchr is 3.71x slower

5. Special Cases and Edge Behaviors

A. Searching for Null Terminator

strrchr can find the null terminator, which returns a pointer to the end of the string.

#include <stdio.h>
#include <string.h>
int main() {
const char *str = "Hello";
// Find the null terminator
char *null_ptr = strrchr(str, '\0');
printf("String: '%s'\n", str);
printf("strrchr(str, '\\0') returns pointer to position %ld\n", 
null_ptr - str);
printf("That's the null terminator at the end\n");
// This can be useful for finding the end of a string
char *end = strrchr(str, '\0');
printf("End of string is at address %p, length %ld\n", 
(void*)end, end - str);
return 0;
}

Output:

String: 'Hello'
strrchr(str, '\0') returns pointer to position 5
That's the null terminator at the end
End of string is at address 0x7ffd1234, length 5

B. Empty String Behavior

#include <stdio.h>
#include <string.h>
int main() {
const char *empty = "";
char *result1 = strrchr(empty, 'a');
char *result2 = strrchr(empty, '\0');
printf("String: '%s' (empty)\n", empty);
printf("strrchr(empty, 'a') = %s\n", result1 ? "NOT NULL" : "NULL");
printf("strrchr(empty, '\\0') = %s\n", result2 ? "pointer to null" : "NULL");
if (result2) {
printf("Pointer to null terminator at offset %ld\n", result2 - empty);
}
return 0;
}

Output:

String: '' (empty)
strrchr(empty, 'a') = NULL
strrchr(empty, '\0') = pointer to null
Pointer to null terminator at offset 0

C. Character Type and Sign Extension

The character argument is passed as int and converted to unsigned char internally.

#include <stdio.h>
#include <string.h>
int main() {
// Extended ASCII/negative char values
unsigned char extended_ascii[] = {0x80, 0xFF, 'A', 'B', 0x80, '\0'};
// Search for 0x80 (128) - extended ASCII
char *result = strrchr((char*)extended_ascii, 0x80);
if (result) {
printf("Found character 0x80 at position %ld\n", 
result - (char*)extended_ascii);
}
// Important: strrchr uses unsigned comparison
// This means that even if char is signed on your system,
// the comparison still works correctly for extended characters
char test = (char)0x80;  // May be negative if char is signed
printf("0x80 as signed char: %d\n", test);
// But strrchr treats it as unsigned char
char *result2 = strrchr((char*)extended_ascii, test);
if (result2) {
printf("Still found! (unsigned comparison)\n");
}
return 0;
}

6. Building Variations of strrchr

A. strrchrnul (GNU extension - find last occurrence or return end pointer)

#include <stdio.h>
// Find last occurrence of c, or return pointer to null terminator
char *my_strrchrnul(const char *str, int c) {
unsigned char target = (unsigned char)c;
const char *last = NULL;
while (*str != '\0') {
if ((unsigned char)*str == target) {
last = str;
}
str++;
}
if (target == '\0') {
return (char*)str;  // null terminator
}
if (last != NULL) {
return (char*)last;
}
return (char*)str;  // Not found, return pointer to null terminator
}
int main() {
const char *str = "Hello World";
char *result1 = my_strrchrnul(str, 'o');
char *result2 = my_strrchrnul(str, 'x');
printf("String: '%s'\n", str);
printf("Last 'o': '%s' (offset %ld)\n", result1, result1 - str);
printf("Search for 'x': returns '%s' (end of string)\n", result2);
return 0;
}

Output:

String: 'Hello World'
Last 'o': 'orld' (offset 7)
Search for 'x': returns '' (end of string)

B. memrchr - Searching in Memory Block (GNU extension)

While not standard C, memrchr searches backward in a memory block (not null-terminated).

#include <stdio.h>
#include <string.h>
// Simple implementation of memrchr (search backward in memory block)
void *my_memrchr(const void *s, int c, size_t n) {
const unsigned char *ptr = (const unsigned char*)s + n;
unsigned char target = (unsigned char)c;
while (ptr-- > (const unsigned char*)s) {
if (*ptr == target) {
return (void*)ptr;
}
}
return NULL;
}
int main() {
char data[] = {10, 20, 30, 40, 50, 30, 60, 70};
size_t size = sizeof(data);
// Find last occurrence of 30 in the array
void *result = my_memrchr(data, 30, size);
if (result) {
size_t pos = (char*)result - data;
printf("Data array: ");
for (size_t i = 0; i < size; i++) {
printf("%d ", data[i]);
}
printf("\nLast 30 found at position %zu (value %d)\n", 
pos, data[pos]);
}
return 0;
}

Output:

Data array: 10 20 30 40 50 30 60 70 
Last 30 found at position 5 (value 30)

C. Case-Insensitive strrchr

#include <stdio.h>
#include <ctype.h>
// Case-insensitive version of strrchr
char *strrchri(const char *str, int c) {
unsigned char target = (unsigned char)tolower(c);
const char *last = NULL;
while (*str != '\0') {
if (tolower((unsigned char)*str) == target) {
last = str;
}
str++;
}
// Check for null terminator if looking for '\0'
if (c == '\0') {
return (char*)str;
}
return (char*)last;
}
int main() {
const char *text = "Hello WORLD, hello world";
printf("Text: '%s'\n", text);
char *result1 = strrchr(text, 'w');
char *result2 = strrchri(text, 'w');
printf("strrchr (case-sensitive) 'w': %s\n", 
result1 ? result1 : "NULL");
printf("strrchri (case-insensitive) 'w': '%s'\n", 
result2 ? result2 : "NULL");
return 0;
}

Output:

Text: 'Hello WORLD, hello world'
strrchr (case-sensitive) 'w': 'world'
strrchri (case-insensitive) 'w': 'world'

7. Real-World Applications

A. URL Path Parser

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
char *protocol;
char *host;
char *path;
char *filename;
char *extension;
} URL;
void parse_url(const char *url, URL *parsed) {
// Initialize to NULL
memset(parsed, 0, sizeof(URL));
// Find protocol separator
char *protocol_sep = strstr(url, "://");
if (protocol_sep) {
size_t protocol_len = protocol_sep - url;
parsed->protocol = malloc(protocol_len + 1);
strncpy(parsed->protocol, url, protocol_len);
parsed->protocol[protocol_len] = '\0';
url = protocol_sep + 3;  // Skip "://"
}
// Find path start (first slash after host)
char *path_start = strchr(url, '/');
if (path_start) {
// Host is everything before the first slash
size_t host_len = path_start - url;
parsed->host = malloc(host_len + 1);
strncpy(parsed->host, url, host_len);
parsed->host[host_len] = '\0';
// Path is from slash to end
parsed->path = strdup(path_start);
// Find last slash for filename
char *last_slash = strrchr(path_start, '/');
if (last_slash && *(last_slash + 1) != '\0') {
parsed->filename = strdup(last_slash + 1);
// Find extension using last dot in filename
char *last_dot = strrchr(parsed->filename, '.');
if (last_dot && last_dot != parsed->filename) {
parsed->extension = strdup(last_dot + 1);
// Truncate filename at dot (optional)
*last_dot = '\0';
}
}
} else {
// No path, entire URL is host
parsed->host = strdup(url);
}
}
void free_url(URL *parsed) {
free(parsed->protocol);
free(parsed->host);
free(parsed->path);
free(parsed->filename);
free(parsed->extension);
}
int main() {
const char *test_urls[] = {
"https://example.com/index.html",
"http://github.com/user/repo/file.tar.gz",
"ftp://server.net/downloads/document.pdf",
"https://api.service.com/v1/users"
};
for (int i = 0; i < 4; i++) {
URL parsed = {0};
parse_url(test_urls[i], &parsed);
printf("\nURL: %s\n", test_urls[i]);
printf("  Protocol: %s\n", parsed.protocol ? parsed.protocol : "(default)");
printf("  Host: %s\n", parsed.host);
printf("  Path: %s\n", parsed.path ? parsed.path : "/");
printf("  Filename: %s\n", parsed.filename ? parsed.filename : "(none)");
printf("  Extension: %s\n", parsed.extension ? parsed.extension : "(none)");
free_url(&parsed);
}
return 0;
}

Output:

URL: https://example.com/index.html
Protocol: https
Host: example.com
Path: /index.html
Filename: index.html
Extension: html
URL: http://github.com/user/repo/file.tar.gz
Protocol: http
Host: github.com
Path: /user/repo/file.tar.gz
Filename: file.tar
Extension: gz
URL: ftp://server.net/downloads/document.pdf
Protocol: ftp
Host: server.net
Path: /downloads/document.pdf
Filename: document.pdf
Extension: pdf
URL: https://api.service.com/v1/users
Protocol: https
Host: api.service.com
Path: /v1/users
Filename: (none)
Extension: (none)

B. Backwards String Search for Pattern

#include <stdio.h>
#include <string.h>
// Find last occurrence of a substring (like reverse strstr)
char *strrstr(const char *haystack, const char *needle) {
if (*needle == '\0') return (char*)haystack;
size_t needle_len = strlen(needle);
const char *last = NULL;
const char *ptr = haystack;
while ((ptr = strstr(ptr, needle)) != NULL) {
last = ptr;
ptr += needle_len;
}
return (char*)last;
}
// Alternative: more efficient backward search
char *strrstr_efficient(const char *haystack, const char *needle) {
if (*needle == '\0') return (char*)haystack;
size_t needle_len = strlen(needle);
size_t haystack_len = strlen(haystack);
if (needle_len > haystack_len) return NULL;
// Search from the end
const char *ptr = haystack + haystack_len - needle_len;
while (ptr >= haystack) {
if (strncmp(ptr, needle, needle_len) == 0) {
return (char*)ptr;
}
ptr--;
}
return NULL;
}
int main() {
const char *text = "The quick brown fox jumps over the lazy dog. The fox is quick.";
const char *patterns[] = {"fox", "the", "dog", "quick", "none"};
printf("Text: '%s'\n\n", text);
for (int i = 0; i < 5; i++) {
char *result = strrstr(text, patterns[i]);
if (result) {
printf("Last '%s': '%s'\n", patterns[i], result);
} else {
printf("Last '%s': not found\n", patterns[i]);
}
}
return 0;
}

Output:

Text: 'The quick brown fox jumps over the lazy dog. The fox is quick.'
Last 'fox': 'fox is quick.'
Last 'the': 'the lazy dog. The fox is quick.'
Last 'dog': 'dog. The fox is quick.'
Last 'quick': 'quick.'
Last 'none': not found

C. Configuration File Parser (Finding Last Value)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
char key[50];
char value[200];
} ConfigEntry;
// Find last occurrence of a key in config lines
ConfigEntry* find_last_config(char **config_lines, int line_count, const char *key) {
ConfigEntry *result = NULL;
for (int i = line_count - 1; i >= 0; i--) {
if (strncmp(config_lines[i], key, strlen(key)) == 0 && 
config_lines[i][strlen(key)] == '=') {
result = malloc(sizeof(ConfigEntry));
if (result) {
strcpy(result->key, key);
// Find value after '='
char *value_start = config_lines[i] + strlen(key) + 1;
// Remove trailing newline if present
size_t val_len = strlen(value_start);
if (val_len > 0 && value_start[val_len - 1] == '\n') {
val_len--;
}
if (val_len >= sizeof(result->value)) {
val_len = sizeof(result->value) - 1;
}
strncpy(result->value, value_start, val_len);
result->value[val_len] = '\0';
}
break;  // Found last occurrence
}
}
return result;
}
int main() {
// Simulate config file lines
char *config[] = {
"debug=false",
"host=localhost",
"port=8080",
"debug=true",      // This overrides the previous
"timeout=30",
"debug=verbose"    // This overrides both previous
};
int line_count = 6;
const char *keys[] = {"debug", "host", "port", "timeout"};
printf("=== Config File Parser (Last Occurrence Wins) ===\n\n");
for (int i = 0; i < 4; i++) {
ConfigEntry *entry = find_last_config(config, line_count, keys[i]);
if (entry) {
printf("%s = %s\n", entry->key, entry->value);
free(entry);
} else {
printf("%s = (not found)\n", keys[i]);
}
}
return 0;
}

Output:

=== Config File Parser (Last Occurrence Wins) ===
debug = verbose
host = localhost
port = 8080
timeout = 30

8. Common Pitfalls and Best Practices

Pitfall 1: Assuming Pointer Arithmetic is Always Safe

#include <stdio.h>
#include <string.h>
int main() {
const char *str = "Hello";
char *last = strrchr(str, 'l');
if (last) {
// This works: printing from last occurrence
printf("From last 'l': '%s'\n", last);
// This is safe: subtracting pointers
printf("Position: %ld\n", last - str);
// DANGEROUS: pointer arithmetic on result without checking bounds
// char *prev_char = last - 1;  // Might be before string start if 'l' is at position 0
// if (prev_char >= str) { ... }  // Must check!
// Safe way:
if (last > str) {
char prev_char = *(last - 1);
printf("Character before last 'l': '%c'\n", prev_char);
}
}
return 0;
}

Output:

From last 'l': 'llo'
Position: 3
Character before last 'l': 'e'

Pitfall 2: Modifying Returned Pointer String

#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
// strrchr returns a pointer INTO the string
char *last_comma = strrchr(str, ',');
if (last_comma) {
printf("Original: '%s'\n", str);
printf("Last comma at: '%s'\n", last_comma);
// MODIFYING the string through the returned pointer is allowed
// (because str is modifiable)
*last_comma = ';';
printf("After modification: '%s'\n", str);
printf("Now last comma (modified): '%s'\n", last_comma);
}
// BUT: If str is a string literal (const), modifying crashes
const char *literal = "Hello, World!";
char *bad_ptr = strrchr(literal, ',');
// *bad_ptr = ';';  // CRASH - writing to read-only memory!
return 0;
}

Output:

Original: 'Hello, World!'
Last comma at: ', World!'
After modification: 'Hello; World!'
Now last comma (modified): '; World!'

Pitfall 3: Confusing strrchr with strchrnul

#include <stdio.h>
#include <string.h>
int main() {
const char *str = "Hello";
// strrchr returns NULL if char not found
char *result1 = strrchr(str, 'x');
// strchrnul (GNU extension) returns pointer to null terminator
// (not standard C, shown for comparison)
printf("strrchr(str, 'x') returns %s\n", result1 ? "non-NULL" : "NULL");
// Safe pattern: always check for NULL
char *last_e = strrchr(str, 'e');
if (last_e) {
printf("Found: '%s'\n", last_e);
} else {
printf("Not found\n");
}
return 0;
}

Best Practices Summary

PracticeWhyExample
Check for NULLstrrchr returns NULL if character not foundif (ptr) { ... }
Use unsigned char for extended ASCIIAvoid sign extension issuesCast to unsigned char
Don't assume pointer is validPointer could be to start of stringCheck ptr > str before ptr-1
Use with modifiable buffers for in-place editsstrrchr returns pointer into original stringWorks with char arrays, not literals
Cache the resultstrrchr scans entire string each callchar *last = strrchr(str, c);
Consider strchr for first occurrencestrrchr scans entire string alwaysUse strchr if you only need first match

9. Complete Reference Table

ScenarioBehaviorReturn Value
Character found at endLast character matchesPointer to that character
Character found multiple timesReturns last occurrencePointer to last match
Character not foundSearches entire stringNULL
Empty string, c != '\0'No characters to searchNULL
Empty string, c == '\0'Null terminator is foundPointer to null terminator
c == '\0'Always finds null terminatorPointer to string end
str == NULLUndefined behaviorCrash
Searching for extended ASCIICompared as unsigned charWorks correctly

10. Complete Working Example

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
// Comprehensive demonstration of strrchr
int main() {
printf("=== C strrchr FUNCTION MASTER DEMO ===\n\n");
// 1. Basic usage
const char *text = "The quick brown fox jumps over the lazy dog";
char search_char = 'o';
char *last_o = strrchr(text, search_char);
printf("1. Basic search:\n");
printf("   Text: '%s'\n", text);
printf("   Last '%c': position %ld - '%s'\n\n", 
search_char, last_o - text, last_o);
// 2. Searching for null terminator
char *end = strrchr(text, '\0');
printf("2. Null terminator search:\n");
printf("   strrchr(text, '\\0') returns pointer to end (offset %ld)\n\n", 
end - text);
// 3. File extension extraction
const char *filenames[] = {"script.sh", "archive.tar.gz", "document", ".hidden"};
printf("3. File extension extraction:\n");
for (int i = 0; i < 4; i++) {
char *dot = strrchr(filenames[i], '.');
if (dot && dot != filenames[i]) {
printf("   '%s' -> extension: '%s'\n", filenames[i], dot + 1);
} else {
printf("   '%s' -> no extension\n", filenames[i]);
}
}
printf("\n");
// 4. Directory path parsing
const char *paths[] = {"/home/user/file.txt", "local/bin/app", "README"};
printf("4. Directory parsing:\n");
for (int i = 0; i < 3; i++) {
char *last_slash = strrchr(paths[i], '/');
if (last_slash) {
printf("   '%s' -> filename: '%s'\n", paths[i], last_slash + 1);
} else {
printf("   '%s' -> no directory\n", paths[i]);
}
}
printf("\n");
// 5. Finding last word (using space)
const char *sentence = "  This is a sentence with multiple words  ";
char *last_space = strrchr(sentence, ' ');
char *last_word = last_space ? last_space + 1 : (char*)sentence;
// Skip trailing spaces
while (*last_word && isspace(*last_word)) last_word++;
printf("5. Last word extraction:\n");
printf("   Sentence: '%s'\n", sentence);
printf("   Last word: '%s'\n", last_word);
printf("\n");
// 6. Character statistics
const char *stats = "abracadabra";
char target = 'a';
size_t count = 0;
// Count occurrences (not efficient, just demonstration)
const char *ptr = stats;
while ((ptr = strchr(ptr, target)) != NULL) {
count++;
ptr++;
}
char *first = strchr(stats, target);
char *last = strrchr(stats, target);
printf("6. Character statistics:\n");
printf("   String: '%s'\n", stats);
printf("   Character: '%c'\n", target);
printf("   Count: %zu\n", count);
printf("   First occurrence: position %ld\n", first - stats);
printf("   Last occurrence: position %ld\n", last - stats);
printf("\n");
// 7. In-place modification
char modifiable[] = "Hello, World! Hello, Universe!";
char *last_comma = strrchr(modifiable, ',');
if (last_comma) {
printf("7. In-place modification:\n");
printf("   Before: '%s'\n", modifiable);
*last_comma = ';';
printf("   After:  '%s'\n", modifiable);
}
printf("\n");
// 8. Case-insensitive version demonstration
const char *mixed_case = "HeLLo WoRLd";
printf("8. Case-insensitive search (custom):\n");
// Simple demonstration - convert to lowercase first
char lower_copy[50];
strcpy(lower_copy, mixed_case);
for (char *p = lower_copy; *p; p++) {
*p = tolower(*p);
}
char *lower_last = strrchr(lower_copy, 'l');
if (lower_last) {
printf("   '%s' -> last 'l' (case-insensitive) at position %ld\n", 
mixed_case, lower_last - lower_copy);
}
printf("\n");
// 9. URL parsing example
const char *url = "https://example.com/path/to/resource.html";
char *last_dot = strrchr(url, '.');
char *last_slash = strrchr(url, '/');
printf("9. URL parsing:\n");
printf("   URL: %s\n", url);
if (last_dot && last_dot > last_slash) {
printf("   Extension: %s\n", last_dot + 1);
}
if (last_slash && *(last_slash + 1)) {
printf("   Resource: %s\n", last_slash + 1);
}
printf("\n");
// 10. ASCII art - finding last in string
const char *alphabet = "abcdefghijklmnopqrstuvwxyz";
printf("10. Alphabet search:\n");
printf("    String: %s\n", alphabet);
for (char c = 'a'; c <= 'z'; c += 5) {
char *found = strrchr(alphabet, c);
if (found) {
printf("    Last '%c' at position %2ld (%s)\n", 
c, found - alphabet, found);
}
}
return 0;
}

Output:

=== C strrchr FUNCTION MASTER DEMO ===
1. Basic search:
Text: 'The quick brown fox jumps over the lazy dog'
Last 'o': position 27 - 'over the lazy dog'
2. Null terminator search:
strrchr(text, '\0') returns pointer to end (offset 44)
3. File extension extraction:
'script.sh' -> extension: 'sh'
'archive.tar.gz' -> extension: 'gz'
'document' -> no extension
'.hidden' -> no extension
4. Directory parsing:
'/home/user/file.txt' -> filename: 'file.txt'
'local/bin/app' -> filename: 'app'
'README' -> no directory
5. Last word extraction:
Sentence: '  This is a sentence with multiple words  '
Last word: 'words'
6. Character statistics:
String: 'abracadabra'
Character: 'a'
Count: 5
First occurrence: position 0
Last occurrence: position 10
7. In-place modification:
Before: 'Hello, World! Hello, Universe!'
After:  'Hello; World! Hello, Universe!'
8. Case-insensitive search (custom):
'HeLLo WoRLd' -> last 'l' (case-insensitive) at position 3
9. URL parsing:
URL: https://example.com/path/to/resource.html
Extension: html
Resource: resource.html
10. Alphabet search:
String: abcdefghijklmnopqrstuvwxyz
Last 'a' at position  0 (abcdefghijklmnopqrstuvwxyz)
Last 'f' at position  5 (fghijklmnopqrstuvwxyz)
Last 'k' at position 10 (klmnopqrstuvwxyz)
Last 'p' at position 15 (pqrstuvwxyz)
Last 'u' at position 20 (uvwxyz)
Last 'z' at position 25 (z)

Conclusion

The strrchr function is an essential tool for string processing in C, particularly when you need to find the last occurrence of a character. Its ability to search from the end makes it perfect for parsing file paths, extracting extensions, and processing log files.

Key Takeaways:

  • strrchr finds the last occurrence of a character (contrast with strchr for first)
  • Returns NULL if the character is not found (except for null terminator)
  • The null terminator \0 is considered part of the string and can be found
  • The character is treated as unsigned char, avoiding sign extension issues
  • The returned pointer is into the original string (modifiable if the string is modifiable)
  • Always check for NULL before dereferencing the return value
  • Performance is O(n) where n is the string length (scans entire string in worst case)
  • Perfect for: file extensions, directory paths, last word extraction, configuration parsing

When to Use strrchr:

  • Extracting file extensions (last dot)
  • Getting filename from path (last slash)
  • Finding last occurrence of a delimiter
  • Parsing URLs and file paths
  • Backward string analysis

When NOT to Use strrchr:

  • When you need the first occurrence (use strchr)
  • When searching for substrings (use strstr with custom backward search)
  • When performance matters and you only need first occurrence
  • When working with non-null-terminated buffers (use memrchr if available)

Mastering strrchr enables elegant solutions to many common string parsing problems that would otherwise require complex manual loops or multiple passes through the string.

Complete Guide to Core & Advanced C Programming Concepts (Functions, Strings, Arrays, Loops, I/O, Control Flow)

https://macronepal.com/bash/building-blocks-of-c-a-complete-guide-to-functions/
Explains how functions in C work as reusable blocks of code, including declaration, definition, parameters, return values, and modular programming structure.

https://macronepal.com/bash/the-heart-of-text-processing-a-complete-guide-to-strings-in-c-2/
Explains how strings are handled in C using character arrays, string manipulation techniques, and common library functions for text processing.

https://macronepal.com/bash/the-cornerstone-of-data-organization-a-complete-guide-to-arrays-in-c/
Explains arrays in C as structured memory storage for multiple values, including indexing, initialization, and efficient data organization.

https://macronepal.com/bash/guaranteed-execution-a-complete-guide-to-the-do-while-loop-in-c/
Explains the do-while loop in C, where the loop body executes at least once before checking the condition.

https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-the-for-loop-in-c/
Explains the for loop in C, including initialization, condition checking, and increment/decrement for controlled iteration.

https://macronepal.com/bash/mastering-iteration-a-complete-guide-to-while-loops-in-c/
Explains the while loop in C, focusing on condition-based repetition and proper loop control mechanisms.

https://macronepal.com/bash/beyond-if-else-a-complete-guide-to-switch-case-in-c/
Explains switch-case statements in C, enabling multi-branch decision-making based on variable values.

https://macronepal.com/bash/mastering-conditional-logic-a-complete-guide-to-if-else-statements-in-c/
Explains if-else statements in C for decision-making and controlling program flow based on conditions.

https://macronepal.com/bash/mastering-the-fundamentals-a-complete-guide-to-arithmetic-operations-in-c/
Explains arithmetic operations in C such as addition, subtraction, multiplication, division, and operator precedence.

https://macronepal.com/bash/foundation-of-c-programming-a-complete-guide-to-basic-input-output/
Explains basic input and output in C using scanf and printf for interacting with users and displaying results.

Online C Code Compiler
https://macronepal.com/free-online-c-code-compiler-2/

Leave a Reply

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


Macro Nepal Helper