How to Reverse a String in C Without strrev()
Reverse a string in portable C17 using in-place swaps or a capacity-checked copy, with safe input handling and clear byte-reversal limits.
To reverse a string in C without a nonstandard strrev() function, measure its length once and swap bytes from opposite ends toward the middle. The loop is small; the important part is supplying writable, null-terminated storage and keeping the terminating null byte in place.
This guide shows an in-place implementation, a capacity-checked copy that preserves the source, and a complete C17 program. It also explains why string literals, empty input, truncated lines, and UTF-8 require deliberate handling rather than a cleverer swap.
The short answer: reverse in place
#include <string.h>
void reverse_in_place(char *text)
{
size_t n = strlen(text);
for (size_t i = 0; i < n / 2; ++i) {
char temporary = text[i];
text[i] = text[n - 1 - i];
text[n - 1 - i] = temporary;
}
}
This function has two important preconditions: text must be non-null, and it must point to writable storage containing a null-terminated string. It changes that storage; it does not allocate a second string or return a new pointer. The loop naturally does nothing for an empty string, and it does not calculate n - 1 until the loop body runs. That detail avoids unsigned underflow for an empty input.
The C strlen reference defines the length as the number of bytes before the first null character. It is not the array capacity, and calling it requires valid terminated input. Measuring once makes the algorithm O(n) time with O(1) extra space. Avoid placing strlen(text) in every loop-condition check; a straightforward implementation would repeatedly scan the string.
size_t, supplied by the included standard header, is the appropriate type for this length. For an odd-length string, the central byte stays where it is. For an even length, the final pair meets beside the middle. Neither case requires a separate branch.
Give the function writable storage
Use a modifiable array when you want to reverse text initialized from a literal:
char text[] = "hello world";
reverse_in_place(text);
puts(text); /* dlrow olleh */
This fragment belongs inside a function, with <stdio.h> included for puts. The initializer creates an array containing the letters, the space, and their terminating null byte. By contrast, a declaration such as const char *text = "hello"; points to a string literal, not a writable scratch buffer.
A pointer to const char communicates that the characters must not be changed. Casting away const, declaring the pointer as char , or passing a literal directly to a function that writes through char does not make the literal writable. Attempting to modify it has undefined behavior. The SEI CERT C rule on string literals explains this distinction and why a writable array is different.
Both forms can be printed, which makes the mistake easy to miss. Printing reads bytes; in-place reversal writes them. Use an array you own for mutation, and use a separate destination when the source is read-only or must remain unchanged.
A complete, runnable C17 program
Save the following as reverse.c. It includes both helpers and a small main that demonstrates mutation and source-preserving copying. Do not paste the earlier helper into this same file again, because that would define it twice.
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
void reverse_in_place(char *text)
{
size_t n = strlen(text);
for (size_t i = 0; i < n / 2; ++i) {
char temporary = text[i];
text[i] = text[n - 1 - i];
text[n - 1 - i] = temporary;
}
}
bool reverse_copy(char *destination, size_t capacity, const char *source)
{
size_t n = strlen(source);
if (n >= capacity) {
return false;
}
for (size_t i = 0; i < n; ++i) {
destination[i] = source[n - 1 - i];
}
destination[n] = '\0';
return true;
}
int main(void)
{
char text[] = "hello world";
const char *source = "C strings";
char output[32];
reverse_in_place(text);
puts(text);
if (!reverse_copy(output, sizeof output, source)) {
fputs("Destination is too small\n", stderr);
return 1;
}
puts(output);
puts(source);
return 0;
}
Compile it with a C17 compiler and warnings enabled:
cc -std=c17 -Wall -Wextra -Wpedantic reverse.c -o reverse
./reverse
Expected output:
dlrow olleh
sgnirts C
C strings
The first array changes. The copied source remains C strings, even though its reversed result is printed separately. The copy function requires non-null pointers, a valid source string, and destination storage that does not overlap the source. Its capacity argument must describe the actual writable space available.
On a compiler supporting AddressSanitizer and UndefinedBehaviorSanitizer, use them as an extra check:
cc -std=c17 -Wall -Wextra -Wpedantic -fsanitize=address,undefined -g reverse.c -o reverse_sanitized
./reverse_sanitized
Sanitizers help find mistakes during testing. A passing run does not prove that every future caller supplies valid pointers or that every possible input has been handled. Keep those contracts explicit.
Why the destination capacity check matters
For a source of length n, a C string destination needs n + 1 bytes: one for each source byte and one for the terminator. A destination with exactly n bytes is too small even though it can hold all the visible ASCII characters.
The successful condition is n < capacity. The code tests n >= capacity before writing anything, leaving the destination untouched on failure. That comparison also avoids first calculating n + 1, an addition that could wrap at an extreme size_t value. A zero-capacity call returns false before writing, provided the pointer preconditions still hold. An empty source requires at least one destination byte for its terminator.
Passing sizeof output works in the complete example because output is an array in that scope. Inside a function receiving a char *, sizeof reports the pointer’s size, not the allocation’s size. Pass capacity alongside a pointer rather than trying to reconstruct it afterward.
The source and destination must not overlap. If you want to reverse the same buffer, use reverse_in_place. If partial overlap is a real requirement, define and implement that contract separately; the copy helper does not promise to support it.
Read a bounded line before reversing it
For interactive input, the reversal is only as safe as the string supplied to it. The C fgets reference explains that it reads at most one less than the supplied capacity, retains a newline when one fits, and null-terminates successfully read data. Check its return value before inspecting the buffer. On an input error, do not assume the buffer is a valid string.
Place this fragment inside main or another function after making reverse_in_place available. It assumes ordinary text input without embedded null bytes and the <stdio.h> and <string.h> headers:
char buffer[256];
if (fgets(buffer, sizeof buffer, stdin) == NULL) {
if (ferror(stdin)) {
fputs("input error\n", stderr);
} else {
fputs("no input\n", stderr);
}
} else {
char *newline = strchr(buffer, '\n');
if (newline != NULL) {
*newline = '\0';
reverse_in_place(buffer);
puts(buffer);
} else if (strlen(buffer) == sizeof buffer - 1) {
/* Conservative policy: reject a line that filled the buffer. */
fputs("input line is too long\n", stderr);
} else {
/* An EOF-terminated line shorter than the limit is complete. */
reverse_in_place(buffer);
puts(buffer);
}
}
This deliberately rejects a line that fills all 255 non-null slots without capturing a newline. It may reject an exact-fit line at end of file, but it does not silently treat a longer line as complete. The simple policy accepts up to 254 data bytes on a newline-terminated line. A production reader can instead look ahead, drain an overlong remainder and report an error, or grow a dynamic buffer. If you accept multiple lines, handle leftover input before the next read.
Do not switch to gets, which was removed from the C standard because it cannot know the destination size. An unbounded %s conversion with scanf is also unsafe, and %s stops at whitespace rather than capturing a sentence containing spaces.
Removing only \n is intentional. It preserves other data. If input can arrive with Windows CRLF endings, decide whether a preceding \r should also be removed. Do not broadly trim spaces or punctuation unless that is part of the application’s requirements.
String length is not buffer size
strlen scans until the first \0; it cannot validate an arbitrary byte buffer. Without a terminator inside readable storage, that scan can run out of bounds before reversal even begins. A bounded input routine and checked return value are therefore not optional details.
An embedded null also ends the string from the library’s perspective. For example, the literal "abc\0def" contains bytes after the first null, but strlen reports 3. These helpers reverse a C string, not a general binary buffer. If binary data is your real input, use an explicit length and a separately specified buffer-reversal interface.
Likewise, the byte at text[n] is the terminator, not the final character to swap. The final data byte is text[n - 1] when the string is nonempty. Including the terminator in the reversal would put a null at the front and make the result appear empty to normal C string functions.
Standard C has no portable strrev()
There is no ISO C strrev() in the standard string library. Some environments offer extensions; Microsoft, for example, documents its runtime’s _strrev function. Code depending on such a routine is tied to the relevant runtime and its naming conventions.
Do not confuse that extension with PHP’s strrev() or C++’s std::reverse. If you are actually writing C++, the C++ string reversal guide covers the appropriate library options. For portable C17, the small helper above makes both the algorithm and its writable-storage contract visible.
Readability matters more than a clever swap. A temporary variable makes the operation clear. XOR swapping adds no useful advantage here, while recursion consumes stack space without improving a straightforward linear task.
Bytes are not Unicode characters
These functions reverse char elements: bytes in a narrow C string. Plain ASCII normally gives the expected visual result. UTF-8 text can use several bytes for one code point, so reversing those bytes can corrupt its encoding.
Switching to wchar_t is not a universal fix. Its representation depends on the implementation, and a user-perceived character may contain several code points, combining marks, or joined emoji components. Human-language reversal requires an explicit encoding and a Unicode-aware library or segmentation strategy. Do not label a byte-reversal helper as grapheme-safe.
For the related distinction between code points and visible clusters, see JavaScript reversal without breaking emoji. Neither C helper performs token-aware word-order reversal or line-order reversal; a newline inside the string moves just like any other byte.
Test the boundaries, not just hello
Before reusing the helpers, test an empty string, one byte, even and odd lengths, spaces, punctuation, and a palindrome. For copying, test success with exactly n + 1 bytes, failure with exactly n bytes, and zero capacity without a write. Confirm that a read-only source can be copied and remains unchanged.
For input handling, try a normal newline-terminated line, a short EOF-terminated line, an empty line, and a line exceeding the chosen limit. Sanitizer-enabled tests of these helpers passed with GCC 13.3.0, but your caller’s storage and input policy remain part of the correctness story.
If output unexpectedly stops after one word, inspect the input routine before blaming the swap loop. If extra characters follow the result, check the terminator and capacity. If a crash happens only for a literal, check whether supposedly writable storage was actually read-only.
For a quick text sample without compiling code, use the Text Reverser tool. In a C program, the reliable approach is equally focused: choose mutation or copying, supply valid terminated input, reserve the null byte, and make the input limit explicit.
Try the Text Reverser tool
Reverse text, words, or letters instantly — free and entirely in your browser.
Open the tool