#include ‹string.h› -> Ловушка отмены: 6?

Я изучаю программирование на c и сталкиваюсь с некоторыми странностями.

Я возился с массивами/строками char и использовал разные функции. Итак, у меня есть эта программа,

#include <stdio.h>
int main(){
    char a[] = {'H','e','y','a','\0'};
    strcat(a,"bro!");
    printf(a);
}

$ gcc -o test.o test.c -Wall
$ ./test.o
Heya bro!
$

работает как положено, но если я добавлю

#include <string.h>

к началу, тогда я понимаю это

$ gcc -o test.o test.c -Wall
$ ./test.o
Abort: trap 6
$

person Paul Myers    schedule 16.02.2018    source источник
comment
Традиционно вызывать исполняемый файл с расширением .o; обычно используется для объектных файлов, а не полных программ. Ваш исходный код не компилируется корректно по правилам C99 или C11. Ваш вызов strcat() вызывает неопределенное поведение; вы пишете за конец вашего массива. С UB разрешено все — сбои, видимость работы, переформатирование жесткого диска.   -  person Jonathan Leffler    schedule 16.02.2018
comment
Неопределенное поведение, которое вы надорвали себе char a[] и, к несчастью, получили желаемый результат с первого раза. Сделайте a достаточно большим, чтобы поместиться в объединенный массив строк.   -  person Justin Randall    schedule 16.02.2018
comment
Меня удивили два разных поведения (я знаю, что UB есть UB), но в большинстве случаев все детерминировано. Если вы не включите string.h, компилятор вызовет strcat. Но если вы включите заголовок, может случиться так, что в данном случае испускаемый вызов является чем-то другим, а не vanilla strcat. На моей машине он вызывает __strcat_chk, которые ведут себя иначе и вызывают прерывание.   -  person Jean-Baptiste Yunès    schedule 16.02.2018


Ответы (2)


strcat пытается добавить символы, но снова размер массива недостаточно велик, чтобы вместить их, что приводит к неопределенному поведению. (Здесь в вашем случае поведение undefined приводит к ошибке, которую вы видели - могло быть так, что все тоже работало.) Потому что strcat пытался писать за пределами массива a. И первым параметром printf должен быть спецификатор формата, например printf("%s",a);.

char a[] = {'H','e','y','a','\0'};

Здесь a имеет 5 элементов. Размер массива 5. Вам нужно взять больший массив.

char a[9] = {'H','e','y','a','\0'};

Если бы вы заставили массив содержать 9 символов, это сработало бы.

person user2736738    schedule 16.02.2018
comment
о, так strcat() не предоставляет дополнительного места для символов, которые вы добавляете. Я все еще не понимаю, почему это сработало, если я не включил string.h - person Paul Myers; 16.02.2018
comment
Это было просто невезение. Цените, когда такие подпрограммы с ошибками не работают, потому что их сложно найти, когда ваш проект больше и происходят странные вещи oO - person MemAllox; 16.02.2018
comment
@PaulMyers, зная, что strcat ничего не дает, он просто добавляет строку к другой существующей строке, не больше и не меньше. Если памяти недостаточно, вам не повезло. Вы должны убедиться, что памяти достаточно. - person Jabberwocky; 16.02.2018

Когда вы инициализируете массив и опускаете размерность, компилятор выводит размерность из числа инициализаторов.
Итак, этот оператор:

char a[] = {'H','e','y','a','\0'};

эквивалентно этому:

char a[5] = {'H','e','y','a','\0'};

strcat() добавляет копию исходной строки к строке назначения. Он не проверяет, достаточно ли большой буфер назначения, чтобы содержать конкатенированную результирующую строку или нет. Если место назначения недостаточно велико для хранения объединенной результирующей строки, это приводит к неопределенному поведению, которое включает в себя то, что он может выполняться неправильно (либо сбой, либо молча генерируя неправильные результаты), или он может делать именно то, что задумал программист.

Вы должны указать размер буфера a, достаточный для хранения объединенной результирующей строки. В вашей программе размер исходной строки bro! равен 4. strcat() заменяет целевой символ null первым символом исходной строки при их объединении, а нулевой символ включается в конец сформированной новой строки. Итак, в вашем случае минимальный размер, необходимый для хранения объединенной результирующей строки, равен 9.

Объявите a как:

char a[9] = {'H','e','y','a','\0'};

должно работать нормально.

Как предложил Дэвид (в комментариях) вместо 9 вы можете взять разумный размер буфера, например 512 или 1024 (например, a[512] или a[1024]). Это поможет вам избежать проблем до некоторой степени. Однако вам нужно позаботиться о проблемах переполнения буфера в вашем коде. Например, вы можете использовать strncat(), что дает вам возможность указать максимальное размер, который вы можете добавить в строку назначения.
Вы можете указать максимальное количество символов, которые будут добавлены как -

(size of destination buffer) - (the length of the destination string) - 1

чтобы избежать переполнения буфера.

person H.S.    schedule 16.02.2018
comment
char a[9] будет работать для текущей цели, но память стоит дешево. Почему бы не предложить разумный размер распределения, который обеспечил бы большую гибкость? Вы не можете изменить размер массива постфактум. - person ad absurdum; 16.02.2018
comment
@DavidBowling: В своем ответе я упомянул: в вашем случае минимальный размер, необходимый для хранения объединенной результирующей строки, равен 9. - person H.S.; 16.02.2018
comment
Да, я это видел. Обычно, особенно среди учащихся, в массивах используются минимальные размеры; но это может привести к неприятностям, и нет причин скупиться. Я обычно использую char buffer[1024] или char buffer[4096] с буферами пользовательского ввода. За это не взимается штраф, и это помогает избежать определенных типов проблем с вводом-выводом. Я считаю, что полезно напомнить учащимся, что можно щедро распределять и что лучше думать о разумных ожиданиях, чем о минимальных потребностях. - person ad absurdum; 16.02.2018