strncpy, приводящий к ошибке сегментации

Я просто возился с strncpy.

Моя программа выглядит так

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;

}

Здесь, прежде чем я сделаю strncpy, я добавил символ «\ 0» в строку «src», длина строки «src» становится 3, целевой массив имеет размер 10. Но в strncpy я поставил количество копируемых байтов как 100 .

Это означает, что моя исходная строка завершается NULL. Теперь strncpy, как и любая строковая функция, должна попытаться скопировать только 3 байта, даже если количество предоставленных мной байтов больше 3 (в данном случае 100). Это происходит, но я тоже получаю ошибку сегментации.

Мой результат показан ниже

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

Почему здесь происходит ошибка сегментации.

Может кто-нибудь помочь мне здесь.


person Himanshu Gupta    schedule 28.12.2012    source источник
comment
Как вы думаете, что он должен делать, и то, что он делает, - это две совершенно разные вещи.   -  person Brian Roach    schedule 28.12.2012


Ответы (4)


Я мог бы указать вам на страницы руководства, веб-сайты и т. Д., Но в конечном итоге важен сам стандарт C. Как часть стандартной библиотеки времени выполнения, использование и поведение определены в C99-§7.23.2.4 как:

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

Описание Функция strncpy копирует не более n символов (символы, следующие за нулевым символом, не копируются) из массива, на который указывает s2, в массив, на который указывает s1. Если копирование происходит между перекрывающимися объектами, поведение не определено. Если массив, на который указывает s2, является строкой, которая короче n символов, нулевые символы добавляются к копии в массиве, на который указывает s1, пока не будут записаны n символов всего.

Возвращает. Функция strncpy возвращает значение s1.

Здесь имеется важная подразумеваемая информация, наиболее важной из которых является: strncpy() НЕ завершает вашу целевую строку нулевым символом, если длина исходной строки (не включая ее завершающий нулевой символ) соответствует или превышает указанную длину целевого буфера).

Более того, хотя это четко указано в стандарте (см. Выше), меня продолжает сбивать с толку, сколько инженеров НЕ знают, что strncpy() заполняет строковый буфер назначения нулевыми символами до тех пор, пока не будет достигнута указанная длина n когда длина исходной строки меньше, чем размер целевого буфера. Отсюда следует неизбежный вывод:

API strncpy() ВСЕГДА будет записывать n символов в указанный адрес по целевому буферу.

В вашем случае, поскольку целевой буфер имеет ширину всего 10 символов, вы пишете 90 дополнительных символов после определенного конца доступной для записи памяти и, таким образом, попадаете в страну неопределенного поведения.

В этот момент вы должны спросить себя: «Так в чем же польза?» Это , возможно, фундаментальный вариант использования. Это позволяет вам копировать до n символов в целевой буфер с предсказуемостью, зная, что вы не перейдете за n символов. Период. Однако в конечном итоге вам нужна строка с завершающим нулем, поэтому правильное использование таково:

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

где N - жесткая длина dst буфера в символах, которая больше или равна 1. Обратите внимание, что dst также может быть указателем динамически выделяемой памяти:

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

С учетом вышеизложенного у вас будет всегда строка с завершающим нулем в dst. Если исходная строка length меньше указанной длины целевого буфера, strncpy() будет заполнять остальную часть буфера нулевыми символами до тех пор, пока общее количество исходных символов-скопированных + хвостовых-заполненных-нулевых- символов равно n, и последний оператор является избыточным. Если длина исходной строки равна или больше длины целевого буфера, strncpy() прекратит копирование по достижении N-1 символа, а последний оператор устанавливает нулевой символ в конце буфера. Это приводит к «сокращенной» строке префикса исходного источника, но, что наиболее важно, это гарантирует, что вы НЕ выйдете за границы вашего целевого буфера с последующим вызовом строкового API, который сканирует терминатор.

Полезность описанной выше техники всегда остается спорной. Я парень C ++, поэтому std::string спасает меня от всего этого безумия. Но реальность такова: иногда вас волнует, не скопировано ли src полностью целиком в dst; иногда нет. Полезность очень ситуативно зависит. Для представления строковых данных в пользовательском интерфейсе это (скорее всего) не имеет значения. Для копирования строки, которая будет использоваться для критических данных, подстрока с частичным префиксом неприемлема. Когда полиция выдаст ордер на арест «Джозефа Джонсона-младшего», будут некоторые объяснения, что делать, когда его отца («Джозеф Джонсон») тащат в тюрьму, потому что в именном буфере программного обеспечения для выдачи ордеров содержалось только 15 символов. .

С учетом всего сказанного, ваша ошибка сегментации сводится к следующему утверждению:

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

Вспомните заявление, выделенное жирным шрифтом выше: «strncpy() ВСЕГДА будет записывать n символов по адресу, указанному в целевом буфере.». Это означает, что приведенный выше код всегда будет записывать 100 символов в целевой буфер, который в вашем случае имеет ширину всего 10 символов, поэтому поведение undefined и, вероятно, ker-boom .

Исправьте это, выполнив следующие действия, если целевой буфер представляет собой массив символов фиксированной длины:

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;

См. Предыдущее использование, чтобы узнать, как это сделать для динамической строки длиной `N символов.

person WhozCraig    schedule 28.12.2012
comment
спасибо @WhozCraig .. очень подробно и описательно .. Я просто дурачился с strncpy, я знаю, как безопасно использовать strncpy. Я просто хотел знать, завершается ли строка 'src' NULL, тогда сколько символов копируется в строку 'dest', если 'n' больше, чем размер строки 'src', а размер строки 'dest' достаточно велик, чтобы удерживать Строка 'src', но меньше 'n'. Описание strncpy на странице MAN было для меня недостаточно ясным. - person Himanshu Gupta; 28.12.2012
comment
strncpy предназначен для заполнения полей фиксированного размера предсказуемо. Это не функция обработки строк. - person vonbrand; 20.01.2013

Из http://www.cplusplus.com/reference/cstring/strncpy/.

char * strncpy (char * назначение, const char * source, size_t num);

Копировать символы из строки Копирует первое число символов источника в место назначения. Если конец исходной строки C (о которой сигнализирует нулевой символ) обнаружен до того, как было скопировано количество символов, пункт назначения дополняется нулями до тех пор, пока в него не будет записано общее количество символов.

Таким образом, хотя длина исходной строки меньше размера целевого буфера, но из-за поведения передачи strncpy, он пытается наложить остальные символы, превышающие размер целевого буфера, вызывая ошибку сегментации

Вместо копирования, превышающего 100 символов, размер должен быть равен максимально допустимому размеру в целевом буфере, поэтому вы могли бы написать

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

или лучше написать _countof макрос

#define _countof(s) (sizeof(s)/sizeof(s[0]))
................
strncpy(s1.from_str,src,_countof(s1.from_str) - 1);
person Abhijit    schedule 28.12.2012
comment
Эта ошибка сегмента происходит на одной машине LINUX, но не происходит на другой машине UNIX. Почему так ? - person Himanshu Gupta; 28.12.2012
comment
@HimanshuGupta: запись вне выделенной памяти - это неопределенное поведение (UB), а Segmentation Fault - один из многих UB включая демонов, вылетающих из вашего носа - person Abhijit; 28.12.2012
comment
@HimanshuGupta: Если ответ помог, попробуйте проголосовать за и принять ответ - person Abhijit; 28.12.2012
comment
@HimanshuGupta: Под капотом происходит то, что strncpy записывает все, что приходит после того, как место скопировано. Что это может быть, зависит от переменных, которые вы определяете, от того, как ваш компилятор размещает данные в памяти, если он удаляет некоторые переменные, потому что знает, что они не используются (если да, зависит от флагов оптимизации), ... и, наконец, о том, что именно перезаписывается (адрес возврата?) и как интерпретируются записанные там байты (если адрес возврата, это может быть недопустимый адрес инструкции, приземление в середине глубокого выполнения или быть безвредным). - person vonbrand; 20.01.2013

См .: http://www.manpagez.com/man/3/strncpy/.

Функции stpncpy () и strncpy () копируют не более n символов из s2 в s1. Если длина s2 меньше n символов, оставшаяся часть s1 заполняется символами `\ 0 '. В противном случае s1 не завершается.

Остальное заполнено ....

So:

strncpy( s1.from_str, src, 10 );
person Mario The Spoon    schedule 28.12.2012
comment
но здесь размер строки src меньше, чем размер строки dest, а n больше размера строки src и строки dest. - person Himanshu Gupta; 28.12.2012
comment
@icepack, потому что 90 байтов после from_str (память, возможно, не принадлежит вам) перезаписываются. А дальше это зависит от настроек компилятора / ОС. ... что случилось - person Mario The Spoon; 28.12.2012
comment
Это правильно, но в ответе ничего из этого не говорится, это просто копия из справки. - person SomeWittyUsername; 28.12.2012
comment
Я просто дурачусь. Это происходит на машине LINUX, но на другой машине UNIX у меня нет ошибок seg. - person Himanshu Gupta; 28.12.2012

strncpy(s1.from_str,src,100);

Почему вы используете 100 в своей функции, from_str и src имеют 10 последовательных выделенных байтов, но вы копируете 100 байтов, что приводит к seg. вина.

использовать как это,

strncpy(s1.from_str,src,10);

person Adeel Ahmed    schedule 28.12.2012
comment
Я просто дурачусь. Это происходит на машине LINUX, но на другой машине UNIX у меня нет ошибок seg. - person Himanshu Gupta; 28.12.2012
comment
Поскольку это приводит к undefined behaviour, вы можете не получить ошибку seg на другом компьютере LINUX, но в вашем случае лучше использовать strcpy. - person Adeel Ahmed; 28.12.2012