C: Запись 4 байтов в область размером 3 переполняет место назначения? - PullRequest
0 голосов
/ 19 мая 2018

Моя простая программа на C выглядит следующим образом.Изначально я определил переменную buf1 с 3 символами.

У меня нет проблем с 2 символами, такими как AB или XY

user@linux:~/c# cat buff.c; gcc buff.c -o buff; echo -e '\n'; ./buff
#include <stdio.h>
#include <string.h>

int main() {
        char buf1[3] = "AB";
        printf("buf1 val:  %s\n", buf1);
        printf("buf1 addr: %p\n", &buf1);
        strcpy(buf1,"XY");
        printf("buf1 val:  %s\n", buf1);
}

buf1 val:  AB
buf1 addr: 0xbfe0168d
buf1 val:  XY
user@linux:~/c# 

К сожалению,когда я добавляю 3 символа, например XYZ, при компиляции программы появляется следующее сообщение об ошибке:

buff.c:8:2: warning: ‘__builtin_memcpy’ writing 4 bytes into a region of size 3 overflows the destination [-Wstringop-overflow=]
  strcpy(buf1,"XYZ");

Разве XYZ не рассматривается как 3 байта?Почему в сообщении об ошибке указано 4 bytes вместо 3 bytes

user@linux:~/c# cat buff.c; gcc buff.c -o buff; echo -e '\n'; ./buff
#include <stdio.h>
#include <string.h>

int main() {
        char buf1[3] = "AB";
        printf("buf1 val:  %s\n", buf1);
        printf("buf1 addr: %p\n", &buf1);
        strcpy(buf1,"XYZ");
        printf("buf1 val:  %s\n", buf1);
}buff.c: In function ‘main’:
buff.c:8:2: warning: ‘__builtin_memcpy’ writing 4 bytes into a region of size 3 overflows the destination [-Wstringop-overflow=]
  strcpy(buf1,"XYZ");
  ^~~~~~~~~~~~~~~~~~


buf1 val:  AB
buf1 addr: 0xbfdb34fd
buf1 val:  XYZ
Segmentation fault
user@linux:~/c# 

Ответы [ 3 ]

0 голосов
/ 19 мая 2018

Вы забыли, что строки C заканчиваются нулем. sizeof "AB" равно 3, а sizeof "XYZ" равно 4 из-за неявного завершающего байта.(Тип строкового литерала "AB" равен char[3], а тип "XYZ" равен char[4].)

Если бы вы не указали никакой длины для buf1,он также будет иметь размер 3 байта:

char buf1[] = "AB";  // here exactly the same as char buf1[3] = "AB";

Структура памяти будет

buf1
  v
  +-------+-------+-------+
  |  [0]  |  [1]  |  [2]  |
  +-------+-------+-------+
  |  'A'  |  'B'  |  '\0' |
  +-------+-------+-------+

Теперь strcpy копирует завершающий нулевой символ (C11 7.24.2.3p2 ):

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

, что означает, что всего скопировано 4 байта, но есть место только для 3 символов, поэтому код имеет неопределенное поведение , и компилятор выдает диагностические сообщения, C11 7.1.4 Использование библиотечных функций стр.2 :

[...] Если аргумент функции описывается как массив, указатель фактически передается функциидолжно иметь такое значение, чтобы все вычисления адресов и доступ к объектам (которые были бы действительными, если бы указатель действительно указывал на первый элемент такого массива) были действительно действительными. [...]

В реальном коде неявный доступ к buf1[3] фактически недействителен .

Структура памяти после strcpy:

buf1
  v
  +-------+-------+-------+-------+
  |  [0]  |  [1]  |  [2]  |  ???  |
  +-------+-------+-------+-------+
  |  'X'  |  'Y'  |  'Z'  |  '\0' |
  +-------+-------+-------+-------+

Причина, по которойпредупреждение __builtin_memcpy вызвано тем, что компилятор C сильно оптимизировал этот код - он заменил strcpy строки известной длины на memcpy известной длины, так как memcpy будет генерировать более эффективный код.


И, наконец, вы можете вставить 3 символа в char buf1[3]; с помощью strncpy, но буфер не может соответствовать завершающему нулевому символу, и, следовательно, он не может быть напечатан с использованием printf("%s"), но вы можете распечатать его с указаниемявная ширина поля, которая меньше или равна длине массива - однако значение на выходе будет дополнено:

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

int main() {
    char buf1[3] = "AB";
    printf("buf1 val:  >%-3s<\n", buf1);
    printf("buf1 addr: %p\n", &buf1);
    strncpy(buf1, "XYZ", 3);
    printf("buf1 val:  >%-3s<\n", buf1);
}

И скомпилируем, запустив его:

% gcc strncpy.c -Wall -Wextra
% ./a.out
buf1 val:  >AB <
buf1 addr: 0x7ffd7f6aecc5
buf1 val:  >XYZ<

, нопосле AB

напечатан один дополнительный пробел
0 голосов
/ 19 мая 2018

7.24.2.3p2 в strcpy:

Функция strcpy копирует строку, на которую указывает s2 (включая завершающий нулевой символ), в массив, на который указывает s1.

3 символа + '\ 0' == 4 символа

Вы также получите 4 из:

printf("%zu\n", sizeof "ABC");

, поскольку строковые литералы в основном являются анонимными символамимассивы литералов со статическим хранилищем, в основном эквивалентные:

 static char const __anonymous[]="ABC"; /*the size gets inferred*/

или

 (char const[]){"ABC"};

(с историческим предупреждением о const, которого на самом деле нет, но для всехнамерения и цели, вы должны притворяться, что это так)

0 голосов
/ 19 мая 2018

Если вы посмотрите на реализацию (и) strcpy, вы увидите, что это зависит от null character.

char *strcpy(char *d, const char *s)
{
   char *saved = d;
   while (*s != '\0')
   {
       *d++ = *s++;
   }
   *d = 0;
   return saved;
}

Итак, для char arr[3], если вы попытаетесь поместить последовательность из трех символовперезаписываешь на '\0'.Более того, он может повторяться навсегда, вызывая переполнение стека Последовательность символов без нулевого терминатора вызывает также неопределенное поведение .

...