Question
How can I convert a std::string to a char* or a const char* in C++?
Short Answer
By the end of this page, you will understand how std::string stores text, how to get a C-style string from it, when to use c_str() or data(), and what to do if you truly need a writable char*. You will also learn the lifetime rules that make this conversion safe or unsafe.
Concept
In C++, std::string and C-style strings are related but not the same thing.
std::stringis a C++ string class that manages memory for you.- A C-style string is typically a
const char*orchar*pointing to a null-terminated character array.
If you have a std::string, the most common way to get a read-only C-style string is:
std::string s = "hello";
const char* p = s.c_str();
c_str() returns a pointer to the string's internal character data as a const char*.
This matters because many older C libraries and some system APIs expect const char* instead of std::string.
If you need writable character data, things are different:
- You usually should not try to directly treat a
std::stringas a writablechar*unless you understand the rules. - If an API only reads the string, use
const char*fromc_str(). - If an API needs to modify the characters, create a writable buffer.
In modern C++, you may also use data():
Mental Model
Think of std::string as a smart container that owns and manages a text buffer for you.
A const char* from c_str() is like borrowing a read-only view of that buffer.
- You can look at it.
- You can pass it to functions that read text.
- You do not own it.
- You should not try to modify it.
A char* is different: it suggests writable memory.
That is like asking for a marker so you can edit the label directly. Sometimes the buffer can be edited safely, but only under specific rules. If you need guaranteed writable memory for another API, it is often safer to create your own separate buffer.
Syntax and Examples
Read-only conversion with c_str()
#include <iostream>
#include <string>
int main() {
std::string name = "Alice";
const char* cstr = name.c_str();
std::cout << cstr << '\n';
}
Use this when the function only needs to read the string.
Using data()
#include <iostream>
#include <string>
int main() {
std::string text = "Hello";
const char* p = text.data();
std::cout << p << '\n';
}
In modern C++, data() is often similar to c_str() for read-only use.
Creating a writable buffer
Step by Step Execution
Consider this example:
#include <iostream>
#include <string>
int main() {
std::string s = "cat";
const char* p = s.c_str();
std::cout << p << '\n';
}
Step by step:
-
std::string s = "cat";- A C++ string object named
sis created. - It stores the text
catand manages its own memory.
- A C++ string object named
-
const char* p = s.c_str();c_str()returns a pointer to the internal character data.pnow points to a null-terminated character sequence representingcat.- The pointer is
const char*, so the data should not be modified throughp.
Real World Use Cases
This conversion is common when C++ code interacts with older APIs or libraries.
Common scenarios
- Calling C library functions such as
puts,fopen, orprintf - Passing file paths to platform APIs that expect
const char* - Interfacing with third-party libraries written in C
- Logging systems that accept C-style strings
- Working with embedded systems or low-level code
Example: opening a file
#include <cstdio>
#include <string>
int main() {
std::string filename = "data.txt";
FILE* file = std::fopen(filename.c_str(), "r");
if (file) {
std::fclose(file);
}
}
fopen expects const char*, so c_str() bridges the gap.
Example: formatting output
Real Codebase Usage
In real projects, developers usually follow a simple pattern:
Keep std::string as the main type
Use std::string for storing, returning, and passing text inside your C++ code.
std::string buildMessage() {
return "Operation completed";
}
Convert only at the boundary where an API requires a C-style string.
std::string msg = buildMessage();
external_log(msg.c_str());
Use conversion at the call site
This keeps ownership clear and reduces bugs.
send_to_c_api(configPath.c_str());
Use guard clauses for empty input
if (path.empty()) {
return;
}
legacy_open(path.c_str());
Use a separate writable buffer when mutation is required
;
buffer.();
(buffer.());
Common Mistakes
1. Modifying the result of c_str()
Broken code:
std::string s = "hello";
char* p = const_cast<char*>(s.c_str());
p[0] = 'H';
Why this is a problem:
c_str()gives read-only access.- Forcing it to
char*withconst_castis unsafe. - Modifying through that pointer can cause undefined behavior.
Avoid it by using a writable buffer instead.
2. Using the pointer after the string changes
Broken code:
std::string s = "hello";
const char* p = s.c_str();
s += " world";
std::cout << p << '\n';
Why this is a problem:
- Changing the string may reallocate its internal buffer.
pmay no longer point to valid data.
Fix:
std::string s = ;
s += ;
* p = s.();
std::cout << p << ;
Comparisons
| Approach | Returns | Writable? | Null-terminated? | Best use |
|---|---|---|---|---|
s.c_str() | const char* | No | Yes | Pass to APIs that only read the string |
s.data() | const char* or char* depending on constness and C++ version | Sometimes | Typically yes in modern use, but use carefully | Access internal buffer when you understand the rules |
&s[0] | char* | Yes | Not the main intent | Direct buffer access for non-empty strings; less clear than |
Cheat Sheet
Quick reference
Get const char* from std::string
std::string s = "hello";
const char* p = s.c_str();
Read-only access with data()
const char* p = s.data();
Writable buffer copy
std::vector<char> buffer(s.begin(), s.end());
buffer.push_back('\0');
char* p = buffer.data();
Rules to remember
c_str()returnsconst char*- Do not modify the result of
c_str() - The pointer stays valid only while the string exists and is not modified in a way that invalidates it
- If the API only reads, use
c_str()
FAQ
When should I use c_str() in C++?
Use c_str() when a function expects a const char* and only needs to read the string.
Can I convert std::string directly to char*?
Not safely in the general case. If you need writable characters, create a writable buffer such as a std::vector<char> copy.
What is the difference between c_str() and data()?
c_str() is specifically for getting a C-style string. data() gives access to the underlying character buffer and may be writable for non-const strings in modern C++.
Is the pointer from c_str() always valid?
No. It is only valid while the std::string object still exists and its internal buffer has not been invalidated by certain modifications.
Can I store the result of c_str() for later use?
Only if the original std::string stays alive and unchanged in a compatible way. Otherwise, the pointer may become invalid.
Mini Project
Description
Build a small C++ program that stores a filename and a user message in std::string, then passes them to functions that simulate old C-style APIs. This project demonstrates the practical difference between passing read-only text with c_str() and preparing a writable buffer when a function needs to modify characters.
Goal
Create a program that safely passes std::string data to both read-only and writable C-style functions.
Requirements
- Create one function that accepts
const char*and prints the text. - Create one function that accepts
char*and changes the first character. - Store the original text in
std::stringvariables. - Use
c_str()for the read-only function. - Use a writable buffer copy for the mutating function.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Base Class Constructor Rules Explained
Learn how C++ base class constructors are called from derived classes, including order, syntax, defaults, and common mistakes.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.