Почему strcpy пропускает первый символ в исходной строке при использовании целевой строки размера 1? - PullRequest
1 голос
/ 05 мая 2019

В C функция strcpy используется для копирования источника в строку назначения.

Но когда я использую массив назначения char размера 1, strcpy правильно копирует источник в место назначения. Но это также меняет исходный массив char. Я хочу понять, как это работает в C.

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

char a[] = "String ABC";
char b[1];

strcpy(b, a);
int i;
// printf("%c\n", *(&(a[0])-1));

printf("%s\n",a);
printf("%s\n",b);

Я ожидаю, что результат будет

String ABC
String ABC

но вывод я получаю

tring ABC
String ABC

Ответы [ 5 ]

3 голосов
/ 05 мая 2019

Проблема в том, что вы копируете в строку длиной 1 байт более длинную строку, что приводит к неопределенному поведению.

Если вы запустите эту программу:

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

int main(int argc, char *argv[])
{
    char a[] = "String ABC";
    char b[1];
    printf("%p\n", &a);
    printf("%p\n", &b);

    strcpy(b, a);
    int i;
    printf("%c\n", *(&(a[0])-1));
    printf("%c\n", a[0]);
    printf("%s\n",a);
    printf("%s\n",b);
    printf("%p\n", &a);
    printf("%p\n", &b);
}

вы увидите b и a имеют смежные адреса, а b сохраняется в адресе памяти до a.Скорее всего, strcpy копирует строку в b, но поскольку b не выделяется для хранения такой длинной строки, она перезаписывает следующую непрерывную ячейку памяти, которая выглядит как a.

Позвольте мнеукажите || ячейку памяти, в которой хранится символ.Предположим, -b- - это ячейка, в которой хранится одна строка длиной в символ.Перед копированием у вас есть

|-b-|---a memory allocation--|
|-b-|S|t|r|i|n|g| |A|B|C|D|\n|

Теперь a копируется в b: вторая ячейка - это ячейка a, которая теперь содержит t

  |--a memory allocation-|
|S|t|r|i|n|g| |A|B|C|D|\n|

Thisэто то, что я предполагаю, что это происходит.Но помните, что копирование более длинной строки в более короткую приведет к неопределенному поведению.

3 голосов
/ 05 мая 2019

C не выполняет проверку границ и позволит вам выйти за границы буфера.Фактическое поведение не определено, но в вашем случае вполне вероятно, что расположение памяти будет таким:

 b a
|-|S|t|r|i|n|g|A|B|C|\0|

После strcpy()

 b a
|S|t|r|i|n|g|A|B|C|\0|\0|

Так что b содержит 'S' и не имеет нулевого терминатора (потому что места нет), поэтому, когда вы печатаете его, он встречается с a, который имеет "tringABC".

Возможны другие результаты в зависимости от того, как компилятор упорядочивает и выравнивает соседниепеременные и как реализация работает с перекрывающимися strcpy() источником и назначением, которое также не определено.

1 голос
/ 05 мая 2019

Забавно, мой компилятор ведет себя по-другому: при компиляции выдает предупреждение:

% gcc strcpy.c -O3
In file included from /usr/include/string.h:494:0,
                 from strcpy.c:1:
In function ‘strcpy’,
    inlined from ‘main’ at strcpy.c:8:5:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:90:10: warning:
         ‘__builtin___memcpy_chk’ writing 11 bytes into a region of size 1 overflows the
         destination [-Wstringop-overflow=]
   return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

И когда я запускаю программу, он прерывает:

% ./a.out                       
*** buffer overflow detected ***: ./a.out terminated
1 голос
/ 05 мая 2019

Вы не можете скопировать a в b, потому что в b недостаточно места.Функция strcpy просто записывает после конца массива, что является неопределенным поведением.Это означает, что программа может вести себя любым непредсказуемым образом (что иногда, если вам не повезло, означает, что она работает так, как вы ожидали).

Другими словами: когда вы используете strcpy, вы должны убедитесь, что буфер назначения достаточно велик, включая нулевой терминатор.В этом конкретном примере это означает, что b должно быть длиной не менее 11 элементов (10 для строки, 1 для нулевого терминатора).

0 голосов
/ 05 мая 2019

Как отметил @Acorn в своем ответе, поведение, которое вы видите, является неопределенным поведением, что означает, что компилятор может генерировать произвольный код.

Однако, если вы хотите исследовать, что здесь происходит (чистодля любопытства), это может помочь распечатать адреса массивов.

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

int main(){
    char a[] = "String ABC";
    char b[1];

    strcpy(b, a);
    int i;
    // printf("%c\n", *(&(a[0])-1));

    printf("%s\n",a);
    printf("%s\n",b);

    printf("%p\n",a);
    printf("%p\n",b);
}

На моей машине вывод выглядит следующим образом.

ring ABC
String ABC
0x7ffc36f1b29d
0x7ffc36f1b29c

Как видите,два указателя массива отличаются только одним.Когда вы копируете источник в место назначения, вы перезаписываете первые N-1 символов исходного массива последними N-1 символами источника, где N - это количество символов в источнике, включая нулевой терминатор.

...