strncpy leading to segmentation fault
Asked Answered
B

4

9

I am just messing around with strncpy.

My program looks like this

typedef struct
{
    char from_str[10];
}test;

main ()
{

    test     s1;
    memset(&s1,0,sizeof(test));
    char       src[10]="himansh";
    char       dest[10];

    memset(dest,0,10);
    src[3]='\0';

    printf("src is %s and strlen is %d \n",
            src,strlen(src));

    fflush(stdout);

    strncpy(s1.from_str,src,100);

    printf("s1.from_str is %s , src is %s \n",
            s1.from_str,src);
    return 1;

}

Here before I do strncpy I have added a "\0" character in "src" string, length of "src" string becomes 3 , destination array is of size 10 .But in strncpy I have put number of bytes to be copied as 100.

This means my source string is NULL terminated. Now strncpy like any string function should try to copy only 3 bytes even if the number of bytes I provide is more than 3 (in this case 100). It does that, but I get a segmentation fault too.

My result is shown below

src is him and strlen is 3
s1.from_str is him , src is him
Segmentation fault (core dumped)

Why is this segmentation fault happening over here.

Can any one help me out here.

Broadbrim answered 28/12, 2012 at 6:12 Comment(1)
What you think it should try to do, and what it does are two completely different things.Execratory
O
18

I could point you to man pages, websites, etc, but ultimately what matters is the C standard itself. As part of the standard runtime library, the usage and behavior is defined in C99-§7.23.2.4 as:

#include <string.h>
char *strncpy(char * restrict s1,
      const char * restrict s2,
      size_t n);

Description The strncpy function copies not more than n characters (characters that follow a null character are not copied) from the array pointed to by s2 to the array pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined. If the array pointed to by s2 is a string that is shorter than n characters, null characters are appended to the copy in the array pointed to by s1, until n characters in all have been written.

Returns The strncpy function returns the value of s1.

There is significant implied information here, the most important being: strncpy() will NOT terminate your destination string with a null character if the source string length (not including its null character terminator) meets or exceeds the specified destination buffer length).

Furthermore, though clearly specified in the standard (see above), it continues to confound me how many engineers are NOT aware that strncpy() tail-fills the destination string buffer with null characters until the specified length n is reached when the source string length is less than the destination buffer size. This draws the following inescapable conclusion:

The strncpy() API will ALWAYS write n characters to the address referenced by the destination buffer.

In your case, because the target-buffer is only 10-chars wide, you're writing 90 additional characters past the defined-end of writable memory, and thus walking into the land of undefined behavior.

At this point you have to be asking yourself "So whats the use?" There is an arguably fundamental use-case. It allows you to copy up to n chars to the target buffer with the predictability of knowing you won't overrun past n chars. Period. Ultimately, though, you want a null-terminated string, so the proper usage is this:

char dst[ N ]; 
strncpy(dst, src, N-1);
dst[N-1] = 0;

where N is the hard-length of the dst buffer in chars and is greater-than-or-equal to 1. Note that dst could just-as-well be a dynamic-allocated memory pointer:

char *dst = malloc( N * sizeof(char) ); 
strncpy(dst, src, N-1);
dst[N-1] = 0;

With the above, you will always have a null-terminated string at dst. If the source string length is smaller than the specified target buffer length, strncpy() will tail-fill the rest of the buffer with null characters until a total of source-chars-copied + tail-filled-null-characters equals n, and the final statement is redundant. If the source string length is equal to or greater than the target buffer length, strncpy() will stop copying once N-1 chars are reached, and the final statement sets a null character at the end of the buffer. This results in a "cut-down" prefix string of the original source, but most important, it ensures you will NOT exceed the boundaries of your target buffer with a later string-API call that scans for a terminator.

The usefulness of the above technique is always debatable. I'm a C++ guy, so std::string saves my happy-self from all this insanity. But the reality is this: Sometimes you care if src isn't copied in its entirety to dst; sometimes you don't. The usefulness is very situationally dependent. For presenting string-data in a UI this won't (likely) matter. For copying a string to be used for critical data, a partial-prefix-substring isn't going to be acceptable. When the police issue an arrest warrant to "Joseph Johnson Jr.", there will be some explaining to do when his father ("Joseph Johnson") is hauled into jail because the name-buffer of the warrant-issuance software only held 15 chars.

All of that said, your segmentation fault comes down to this statement:

strncpy(s1.from_str,src, 100); // length parameter is wrong.

Recall the bold statement above: "strncpy() will ALWAYS write n characters to the address referenced by the destination buffer.". This means the above code will always write 100 chars to the target buffer, which in your case is only 10-chars wide, thus undefined behavior and likely ker-boom.

Rectify this by doing the following if the target buffer is a fixed-length character array:

strncpy(s1.from_str,src, sizeof(s1.from_str)/sizeof(s1.from_str[0])-1);
s1.from_str[ sizeof(s1.from_str)/sizeof(s1.from_str[0])-1 ] = 0;

See the prior usage for how to do this for dynamic string of length `N chars.

Obeng answered 28/12, 2012 at 8:59 Comment(2)
thnx @Obeng .. very detailed and descriptive .. I was just fooling around with strncpy , I know how to use strncpy safely . I just wanted to know if 'src' string is NULL terminated then how many characters are copied into 'dest' string if 'n' is more than the size of 'src' string and size of 'dest' string is big enough to hold 'src' string but smaller than 'n'. MAN page description of strncpy was not clear enough to me ..Broadbrim
strncpy is for filing in fixed size fields predictably. It is not a "string handling function".Beauharnais
K
6

From http://www.cplusplus.com/reference/cstring/strncpy/

char * strncpy ( char * destination, const char * source, size_t num );

Copy characters from string Copies the first num characters of source to destination. If the end of the source C string (which is signaled by a null-character) is found before num characters have been copied, destination is padded with zeros until a total of num characters have been written to it.

So though the source string length is less than the size of the destination buffer size, yet because of the passing behavior of strncpy, it tries to overlay the rest of the characters beyond the destination buffer size causing Segmentation fault

Instead of copying beyond the 100 characters' the size should be equal to the maximum permissible size in the destination buffer, so, you could have written

strncpy(s1.from_str,src,sizeof(s1.from_str)/sizeof(s1.from_str[0]) - 1); Actual size -1 to accomodate the null terminator 

or better to write a _countof macro

#define _countof(s) (sizeof(s)/sizeof(s[0]))
................
strncpy(s1.from_str,src,_countof(s1.from_str) - 1);
Kauffman answered 28/12, 2012 at 6:16 Comment(4)
This seg fault is happening in one LINUX machine , but its not happening in a different UNIX machine. Why so ?Broadbrim
@HimanshuGupta: Writing beyond the allocated memory is an undefined behavior(UB) and Segmentation Fault is one of the many UBs including demons flying out of your noseKauffman
@HimanshuGupta: If the answer helped, try upvoting and accepting the answerKauffman
@HimanshuGupta: What happens under the hood is that strncpy is writing over whatever comes after the place copied too. What that might be depends on the variables you define, how your compiler lays out data in memory, if it deletes some variables because it knows they aren't used (if so, depends on the optimization flags), ..., and finally on exactly what gets overwritten (return address?) and what the bytes written there get interpreted as (if return address, it might be an illegal instruction address, land in the middle of deep do-do, or be harmless).Beauharnais
J
1

See: http://www.manpagez.com/man/3/strncpy/

The stpncpy() and strncpy() functions copy at most n characters from s2 into s1. If s2 is less than n characters long, the remainder of s1 is filled with `\0' characters. Otherwise, s1 is not terminated.

The remainder is filled....

So:

strncpy( s1.from_str, src, 10 );
Jacquelynejacquelynn answered 28/12, 2012 at 6:15 Comment(4)
but here size of src string is less than that of dest string and n is more than the size of both src and dest string.Broadbrim
@icepack because 90 bytes after from_str (memory not belongig to you, possibly) are overwritten. And then it depends ont the compiler settings/ OS. ... what hdoes happenJacquelynejacquelynn
That's correct, but the answer doesn't specify any of this, it just a copy from the referenceSeem
I am just fooling around. This is happening on a LINUX machine but I am not getting any seg fault on another UNIX machine.Broadbrim
S
0

strncpy(s1.from_str,src,100);

Why are you using 100 in your function, from_str and src both have 10 consecutive bytes allocated, but you are copying 100 bytes, which is leading to seg. fault.

use like this,

strncpy(s1.from_str,src,10);

Salish answered 28/12, 2012 at 6:20 Comment(2)
I am just fooling around. This is happening on a LINUX machine but I am not getting any seg fault on another UNIX machine.Broadbrim
Because this lead to undefined behaviour, you may not get seg fault on other LINUX machine, but its better to use strcpy in your case.Salish

© 2022 - 2024 — McMap. All rights reserved.