Почему это намеренно неправильное использование strcpy не терпит неудачу ужасно? - PullRequest
10 голосов
/ 21 августа 2011

Почему приведенный ниже код C, использующий strcpy, прекрасно работает для меня?Я попытался заставить его выйти из строя двумя способами:

1) Я попытался strcpy из строкового литерала в выделенную память, которая была слишком мала, чтобы содержать ее.Он скопировал все это и не пожаловался.

2) Я попытался strcpy из массива, который не был NUL -определен.strcpy и printf работали просто отлично.Я думал, что strcpy скопировал char с, пока не был найден NUL, но ни один не присутствовал, и он все равно остановился.Мне просто везет, или я неправильно понимаю, как работает эта функция?Это специфично для моей платформы (OS X Lion), или большинство современных платформ работают таким образом?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *src1 = "123456789";
    char *dst1 = (char *)malloc( 5 );

    char src2[5] = {'h','e','l','l','o'};
    char *dst2 = (char *)malloc( 6 );

    printf("src1: %s\n", src1);
    strcpy(dst1, src1);
    printf("dst1: %s\n", dst1);
    strcpy(dst2, src2);
    printf("src2: %s\n", src2);
    dst2[5] = '\0';
    printf("dst2: %s\n", dst2);

    return 0;
}

Результат выполнения этого кода:

$ ./a.out   
src1: 123456789
dst1: 123456789
src2: hello 
dst2: hello

Ответы [ 5 ]

18 голосов
/ 21 августа 2011

Сначала копируем в слишком маленький массив:

C не имеет защиты для преодоления границ массива, поэтому, если в dst1[5..9] нет ничего чувствительного, то вам повезло, и копия попадает в память, которой вы не владеете по праву, но она также не падает , Однако эта память небезопасна, поскольку она не была выделена для вашей переменной. Другой переменной вполне может быть выделена эта память, и позже она перезапишет данные, которые вы туда поместили, что позже повредит вашу строку.

Во-вторых, копирование из массива, который не заканчивается нулем:

Несмотря на то, что нас обычно учат, что память полна произвольных данных, ее огромные порции обнуляются. Даже если вы не поставили нулевой терминатор в src2, велика вероятность, что src[5] все равно окажется \0. Это делает копию успешной. Обратите внимание, что это НЕ гарантировано и может дать сбой при любом запуске, на любой платформе, в любое время. Но вам повезло на этот раз (и, вероятно, большую часть времени), и это сработало.

14 голосов
/ 21 августа 2011

Перезапись за пределами выделенной памяти вызывает Неопределенное поведение .
Так что, в некотором смысле, вам повезло.

Неопределенное поведение означает, что все может произойти, и поведение не может быть объяснено как Стандарт, который определяет правила языка, не определяет никакого поведения.

РЕДАКТИРОВАТЬ:
По поводу второй мысли, я бы сказал, что вы действительно Не повезло здесь, что программа работает нормально и не падает.Он работает сейчас, но это не значит, что он будет работать всегда. На самом деле, это бомба, взрывающаяся.

Согласно Закон Мерфи :
" Все, что может пойти не так, пойдет не так "[" и, скорее всего, в самый неудобный момент "]

[ ] - Является ли мое правление законом:)

4 голосов
/ 21 августа 2011

Как сказал @Als, это неопределенное поведение . Это может привести к сбою, но это не обязательно .

Многие диспетчеры памяти выделяют большие куски памяти и затем передают ее «пользователю» меньшими кусками, вероятно, с кратностью 4 или 8 байт. Таким образом, ваша запись за границу, вероятно, просто записывает в дополнительные байты. Или перезаписывает одну из ваших переменных.

4 голосов
/ 21 августа 2011

Да, вам просто везет.

Как правило, куча смежна.Это означает, что когда вы пишете за пределами malloc памяти, вы можете повредить следующий блок памяти или некоторые внутренние структуры данных, которые могут существовать между блоками пользовательской памяти.Такое повреждение часто проявляется спустя долгое время после кода, вызывающего сбой, что затрудняет отладку ошибок этого типа.

Вы, вероятно, получаете NUL s, потому что память оказывается заполненной нулями (что негарантировано).

1 голос
/ 21 августа 2011

Тебе не достаточно malloc байтов.Первая строка "123456789" составляет 10 байтов (присутствует нулевой терминатор), а {'h','e','l','l','o'} составляет 6 байтов (опять же, освобождая место для нулевого терминатора).Вы в настоящее время забиваете память этим кодом, что приводит к неопределенному (то есть странному) поведению.

...