The other answers already told you the bitter truth: copying overlapping strings with strncpy
and strcpy
is undefined behaviour, and shall be avoided, especially when it comes to more complex formats (and it is also true for functions such as sprintf
).
Anyway what you see can be explained analyzing your code step by step. I want to underline again that when there's undefined behaviour any compiler could choose to behave differently, so we cannot be sure that it is an universal explanation.
The important thing to take into account is that all the pointers share the same memory locations. After the initialization of s1
char s1[]="Short Message Service", *s2, *s3;
the char array pointed by it looks like this:
----------------------------------------------
|S|h|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e|\0|
----------------------------------------------
^
s1
Then you set s2
and s3
at the beginning of the second and the third word:
s2=strchr(s1,'M');
s3=strrchr(s2,'S');
Here how the three pointers are located
----------------------------------------------
|S|h|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e|\0|
----------------------------------------------
^ ^ ^
s1 s2 s3
Since every string is actually the array from the respective pointer to the first terminator, if you print the three strings you see:
s1: "Short Message Service"
s2: "Message Service"
s3: "Service"
Then you copy just one character of s2
after the first character of s1
:
strncpy(s1+1,s2,1);
Please note that when the source string is longer than the maximum length passed to strncpy the string terminator is not copied. The array will look like this:
----------------------------------------------
|S|M|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e|\0|
----------------------------------------------
^ ^ ^
s1 s2 s3
Not much would change printing the strings: s1
just became "Short Message Service"
. Finally you use
strcpy(s1+2,s3);
-----------------------------------------------
|S|M|S|e|r|v|i|c|e|\0|a|g|e| |S|e|r|v|i|c|e|\0|
-----------------------------------------------
^ ^ ^
s1 s2 s3
That's why you get
Since every string is actually the array from the respective pointer to the first terminator, if you print the three strings you see:
s1: "SMService"
s2: "ice" // Because of the terminator in the middle
s3: "Service" // The original string ending
Just a practical suggestion
If you need a pointer to every word you just need to store the beginning of the word, as you already do, and then to place a string terminator in the position of each space.
In this way, s1
will be "Short"
(because the terminator will be found where the first space was), s2
will be "Message"
(because the terminator will be found where the second space was) and s3
will be "Service"
(because of the original terminator).
By the way: that's what strtok
does: finding the occurrence of a token, placing a string terminator in it and returning the pointer past it.