-3

I have a function f() which receives a char* p and gives a const char* to it.

void f(char *p) {
  string s = "def";
  strcpy(p, s.c_str());
}

In the main() below I expect to get q = "def".

int main(){
  char *q = "abc";
  f(q);
  cout << q << endl;
}

By running this I get segmentation fault and as I am new in C++ I don't understand why.

I also get a segmentation fault when I do not initialize q thus:

int main(){
  char *q;
  f(q);
  cout << q << endl;
}

Knowing that the function's parameter and the way it's called must not change. Is there any work around that I can do inside the function? Any suggestions? Thanks for your help.

CashCow
  • 30,981
  • 5
  • 61
  • 92
zuubs
  • 149
  • 4
  • 18
  • 2
    Attempting to modify a string literal is undefined behavior. – T.C. Sep 17 '14 at 09:37
  • There are about a hundred duplicates of this, the most immediate I found [**here**](http://stackoverflow.com/questions/1614723/why-is-this-string-reversal-c-code-causing-a-segmentation-fault). (C language, but the premise is the same). Enable your compiler warnings. – WhozCraig Sep 17 '14 at 09:44
  • I edited the question to include both questions the user asked, as both have been answered. – CashCow Sep 17 '14 at 09:56

6 Answers6

4

You are trying to change a string literal. Any attemp to change a string literal results in undefined behaviour of the program.

Take into account that string literals have types of constant character arrays. So it would be more correct to write

  const char *q = "abc";

From the C++ Standard (2.14.5 String literals)

8 Ordinary string literals and UTF-8 string literals are also referred to as narrow string literals. A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration

You could write your program the following way

//...
void f(char *p) {
  string s = "def";
  strcpy(p, s.c_str());
}
//..
main(){
  char q[] = "abc";
  f(q);
  cout << q << endl;
}

If you need to use a pointer then you could write

//...
void f(char *p) {
  string s = "def";
  strcpy(p, s.c_str());
}
//..
main(){
  char *q = new char[4] { 'a', 'b', 'c', '\0' };
  f(q);
  cout << q << endl;
  delete []q;  
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
2

This is an issue that, in reality, should fail at compilation time but for really old legacy reasons they allow it.

"abc" is not not a mutable string and therefore it should be illegal to point a mutable pointer to it.

Really any legacy code that does this should be forced to be fixed, or have some pragma around it that lets it compile or some permissive flag set in the build.

But a long time ago in the old days of C there was no such thing as a const modifier, and literals were stored in char * parameters and programmers had to be careful what they did with them.

The latter construct, where q is not initialised at all is an error because now q could be pointing anywhere, and is unlikely to be pointing to a valid memory place to write the string. It is actually undefined behaviour, for obvious reason - who knows where q is pointing?

The normal construct for such a function like f is to request not only a pointer to a writable buffer but also a maximum available size (capacity). Usually this size includes the null-terminator, sometimes it might not, but either way the function f can then write into it without an issue. It will also often return a status, possibly the number of bytes it wanted to write. This is very common for a "C" interface. (And C interfaces are often used even in C++ for better portability / compatibility with other languages).

To make it work in this instance, you need at least 4 bytes in your buffer.

int main()
{
    char q[4];
    f(q);
    std::cout << q << std::endl;
}

would work.

Inside the function f you can use std::string::copy to copy into the buffer. Let's modify f. (We assume this is a prototype and in reality you have a meaningful name and it returns something more meaningful that you retrieve off somewhere).

size_t f( char * buf, size_t capacity )
{
    std::string s = "def";
    size_t copied = s.copy( buf, capacity-1 ); // leave a space for the null
    buf[copied] = '\0'; // std::string::copy doesn't write this
    return s.size() + 1; // the number of bytes you need
}

int main()
{
     char q[3];
     size_t needed = f( q, 3 );
     std::cout << q << " - needed " << needed << " bytes " << std::endl;
}

Output should be:

de needed 4 bytes

In your question you suggested you can change your function but not the way it is called. Well in that case, you actually have only one real solution:

void f( const char * & p )
{
   p = "def";
}

Now you can happily do

int main()
{
   const char * q;
   f( q );
   std::cout << q << std::endl;
}

Note that in this solution I am actually moving your pointer to point to something else. This works because it is a static string. You cannot have a local std::string then point it to its c_str(). You can have a std::string whose lifetime remains beyond the scope of your q pointer e.g. stored in a collection somewhere.

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • I have removed "abc" from q, I still have the segmentation fault. – zuubs Sep 17 '14 at 09:47
  • Yes but people have answered the original question and now the answer looks wrong because it doesn't address your original question. – CashCow Sep 17 '14 at 09:49
0

Look at the warnings you get while compiling your code (and if you don’t get any, turn up the warning levels or get a better compiler).

You will notice that despite the type declaration, the value of q is not really mutable. The compiler was humoring you because not doing so would break a lot of legacy code.

microtherion
  • 3,938
  • 1
  • 15
  • 18
  • Were string literals in C++ ever mutable? – Maxim Egorushkin Sep 17 '14 at 09:39
  • @microtherion: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings] – zuubs Sep 17 '14 at 09:42
  • @Maxim Yegorushkin: No, but assigning a literal to a mutable `char*` (without trying to change it via the pointer!) is valid in C and C++ adopted it for compatibility. I don't know why it was valid in C though. But I'd guess many pre-standard C compilers allowed it and also allowed really mutating the literal and thus C already adpoted it for compatibility. – Paul Groke Sep 17 '14 at 09:42
  • @MaximYegorushkin - I believe the early versions of the Visual C++ compiler (back in 16-bit days) had string literals that were mutable. – PaulMcKenzie Sep 17 '14 at 09:45
  • @PaulGroke - Many MSDOS based C compilers had mutable strings. Problem is that many DOS based programs being ported to Unix would seg fault due to the modification of string literals. – PaulMcKenzie Sep 17 '14 at 09:49
  • @PaulMcKenzie I guess this explains why I don't see many DOS games on Android. – Maxim Egorushkin Sep 17 '14 at 09:54
  • @MaximYegorushkin - Even before Android. It was in the 1980's and 90's that porting DOS programs to Unix could be counted on to seg fault due to modifying a string literal. – PaulMcKenzie Sep 17 '14 at 09:59
  • I often wonder whether arguments to main are really mutable and what happens if you try changing them. – CashCow Sep 17 '14 at 10:05
  • @CashCow On Linux, command line arguments are on the stack, so you can change them. – Maxim Egorushkin Sep 17 '14 at 10:34
  • @PaulMcKenzie - not only older MSVC versions. Even MSVC 2013 still has the `/GF` option to enable read-only string pooling. Without it, duplicated literals are really duplicated (=huge binaries) and are writable. `/GF` is default under `/O1` and `/O2`, but you can even today compile without it if you like/need. Sad but true. – Paul Groke Sep 17 '14 at 12:20
0

You can't do that because you assigned a string literal to your char*. And this is memory you can't modify.

JBL
  • 12,588
  • 4
  • 53
  • 84
0

With your f, You should do

int main(){
  char q[4 /*or more*/];
  f(q);
  std::cout << q << std::endl;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
-4

The problem is that you are trying to write on a read-only place in the process address space. As all the string literals are placed in read-only-data. char *q = "abc"; creates a pointer and points towards the read-only section where the string literal is placed. and when you copy using strcpy or memcpy or even try q[1] = 'x' it attempts to write on a space which is write protected.

This was the problem among many other solutions one can be

main(){
  char *q = "abc";    \\ the strings are placed at a read-only portion 
                      \\ in the process address space so you can not write that
  q = new char[4];    \\ will make q point at space at heap which is writable
  f(q);
  cout << q << endl;

  delete[] q;
}

the initialization of q is unnecessary here. after the second line q gets a space of 4 characters on the heap (3 for chars and one for null char). This would work but there are many other and better solutions to this problem which varies from situation to situation.

theadnangondal
  • 1,546
  • 3
  • 14
  • 28
  • 1
    Your q is allocated but not deallocated, potential memory leak case – Dr. Debasish Jana Sep 17 '14 at 09:52
  • 1
    Remember to add an extra character for the null terminator - i.e. `char[4]` – Component 10 Sep 17 '14 at 09:53
  • This is a poor answer and has got 2 downvotes so not sure why the user has accepted it., It is not a good idea really to use new here and 3 is too few bytes anyway – CashCow Sep 17 '14 at 10:10
  • And your main is declared incorrectly. Basically this answer is no good. – David Heffernan Sep 17 '14 at 10:21
  • thankyou for your comments .... My purpose was not to provide with an answer infact i was showing what is wrong with the code. So I never cared about deleting the allocated space or the allocated memory. I admit that several people have answered better then me ... especially char q[] = "abc";@CashCow – theadnangondal Sep 17 '14 at 10:44
  • @theadnangondal if that is the case, then provide that explanation in the answer. As it is, you provide code that will not work in the way that the questioner intended (i.e. you change the pointer to `"abc"` to point elsewhere entirely, making the initialisation redundant, and so the input to the function will be different). A better formed answer would probably say 1) these are your problems: and 2) this is how you fix it: – Baldrickk Sep 17 '14 at 11:04