Я написал сопровождающий код в марте 2018 года, чтобы убедиться, что происходит с strncat()
для другого вопроса, который был удален до того, как я отправил ответ. Это просто ретаргетинг этого кода.
Функция strncat()
является (как я сказал в комментарии ) злом и мерзостью. Он также несовместим с интерфейсом strncpy()
и отличается от всего, с чем вы столкнетесь где-либо еще. Прочитав это, вы решите (с удачей), что никогда не должны использовать strncat()
.
TL; DR - Никогда не использовать strncat()
Стандарт C определяет strncat()
(и POSIX соглашается - strncat()
)
C11 §7.24.3.2 Функция strncat
Синопсис
#include <string.h>
char *strncat(char * restrict s1, const char * restrict s2, size_t n);
Описание
Функция strncat
добавляет не более чем n
символов (нулевой символ и символы, следующие за ним, не добавляются) из массива, на который указывает s2
, до конца строки, на которую указывает s1
, Начальный символ s2
заменяет нулевой символ в конце s1
. Завершающий нулевой символ всегда добавляется к результату. 309) Если копирование происходит между перекрывающимися объектами, поведение не определено.
Returns
Функция strncat
возвращает значение s1
.
309) Таким образом, максимальное количество символов, которые могут оказаться в массиве, на который указывает s1
, равно strlen(s1)+n+1
.
Сноска идентифицирует самую большую ловушку с strncat()
- вы не можете безопасно использовать:
char *source = …;
char target[100] = "";
strncat(target, source, sizeof(target));
Это противоречит тому, что происходит с большинством других функций, которые принимают аргумент размера массива 1 в коде C.
Чтобы безопасно использовать strncat()
, вы должны знать:
target
sizeof(target)
- или, для динамически выделенного пространства, выделенная длина
strlen(target)
- вы должны знать длину того, что уже находится в целевой строке
source
strlen(source)
- если вас беспокоит, была ли усечена исходная строка; не нужно, если вам все равно
С этой информацией вы можете использовать:
strncat(target, source, sizeof(target) - strlen(target) - 1);
Однако делать это было бы немного глупо; если вы знаете strlen(target)
, вы можете не заставить strncat()
найти его снова, используя:
strncat(target + strlen(target), source, sizeof(target) - strlen(target) - 1);
Обратите внимание, что strncat()
гарантирует нулевое завершение, в отличие от strncpy()
. Это означает, что вы можете использовать:
size_t t_size = sizeof(target);
size_t t_length = strlen(target);
strncpy(target + t_length, source, t_size - t_length - 1);
target[t_size - 1] = '\0';
и вам будет гарантирован тот же результат, если исходная строка будет слишком длинной для добавления к цели.
Демо-код
Несколько программ, иллюстрирующих аспекты strncat()
. Обратите внимание, что в macOS есть макроопределение strncat()
в <string.h>
, которое вызывает другую функцию - __builtin___strncat_chk
- которая проверяет использование strncat()
. Для компактности командных строк я отбросил две опции компиляции предупреждений, которые я обычно использую - -Wmissing-prototypes -Wstrict-prototypes
- но это не влияет ни на одну из компиляций.
strncat19.c
Это демонстрирует одно безопасное использование strncat()
:
#include <stdio.h>
#include <string.h>
int main(void)
{
char spare1[16] = "abc";
char buffer[16] = "";
char spare2[16] = "xyz";
strncat(buffer, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", sizeof(buffer) - 1);
printf("%zu: [%s]\n", strlen(buffer), buffer);
printf("spare1 [%s]\n", spare1);
printf("spare2 [%s]\n", spare2);
return 0;
}
Он компилируется чисто (с Apple * clang
из XCode 10.1 (Apple LLVM version 10.0.0 (clang-1000.11.45.5)
) и GCC 8.2.0, даже если установлены строгие предупреждения:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror strncat19.c -o strncat19
$ ./strncat19
15: [ABCDEFGHIJKLMNO]
spare1 [abc]
spare2 [xyz]
$
strncat29.c
Это похоже на strncat19.c
, но (a) позволяет указать строку, которая будет скопирована в командной строке, и (b) неправильно использует sizeof(buffer)
вместо sizeof(buffer) - 1
для длины.
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
const char *data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (argc == 2)
data = argv[1];
char spare1[16] = "abc";
char buffer[16] = "";
char spare2[16] = "xyz";
strncat(buffer, data, sizeof(buffer));
printf("%zu: [%s]\n", strlen(buffer), buffer);
printf("spare1 [%s]\n", spare1);
printf("spare2 [%s]\n", spare2);
return 0;
}
Этот код не компилируется со строгими параметрами предупреждения:
$ clang -O3 -g -std=c11 -Wall -Wextra -Werror strncat29.c -o strncat29
strncat29.c:12:27: error: the value of the size argument in 'strncat' is too large, might lead to a buffer
overflow [-Werror,-Wstrncat-size]
strncat(buffer, data, sizeof(buffer));
^~~~~~~~~~~~~~
strncat29.c:12:27: note: change the argument to be the free space in the destination buffer minus the terminating null byte
strncat(buffer, data, sizeof(buffer));
^~~~~~~~~~~~~~
sizeof(buffer) - strlen(buffer) - 1
1 error generated.
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror strncat29.c -o strncat29
In file included from /usr/include/string.h:190,
from strncat29.c:2:
strncat29.c: In function ‘main’:
strncat29.c:12:5: error: ‘__builtin___strncat_chk’ specified bound 16 equals destination size [-Werror=stringop-overflow=]
strncat(buffer, data, sizeof(buffer));
^~~~~~~
cc1: all warnings being treated as errors
$
Даже при отсутствии запрошенных предупреждений GCC выдает предупреждение, но поскольку опция -Werror
отсутствует, она создает исполняемый файл:
$ gcc -o strncat29 strncat29.c
In file included from /usr/include/string.h:190,
from strncat29.c:2:
strncat29.c: In function ‘main’:
strncat29.c:12:5: warning: ‘__builtin___strncat_chk’ specified bound 16 equals destination size [-Wstringop-overflow=]
strncat(buffer, data, sizeof(buffer));
^~~~~~~
$ ./strncat29
Abort trap: 6
$ ./strncat29 ZYXWVUTSRQPONMK
15: [ZYXWVUTSRQPONMK]
spare1 [abc]
spare2 [xyz]
$ ./strncat29 ZYXWVUTSRQPONMKL
Abort trap: 6
$
Это функция __builtin__strncat_chk
на работе.
strncat97.c
Этот код также принимает необязательный строковый аргумент; он также обращает внимание на то, есть ли другой аргумент в командной строке, и, если это так, он напрямую вызывает функцию strncat()
, вместо того, чтобы позволить макросу сначала проверить его:
#include <stdio.h>
#include <string.h>
/*
** Demonstrating that strncat() should not be given sizeof(buffer) as
** the size, even if the string is empty to start with. The use of
** (strncat) inhibits the macro expansion on macOS; the code behaves
** differently when the __strncat_chk function (on High Sierra or
** earlier - it's __builtin__strncat_chk on Mojave) is called instead.
** You get an abort 6 (but no other useful message) when the buffer
** length is too long.
*/
int main(int argc, char **argv)
{
const char *data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (argc >= 2)
data = argv[1];
char spare1[16] = "abc";
char buffer[16] = "";
char spare2[16] = "xyz";
size_t len = (argc == 2) ? sizeof(buffer) : sizeof(buffer) - 1;
if (argc < 3)
strncat(buffer, data, len);
else
(strncat)(buffer, data, len);
printf("buffer %2zu: [%s]\n", strlen(buffer), buffer);
printf("spare1 %2zu: [%s]\n", strlen(spare1), spare1);
printf("spare2 %2zu: [%s]\n", strlen(spare2), spare2);
return 0;
}
Теперь компиляторы pпривести к различным результатам:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror strncat97.c -o strncat97
strncat97.c: In function ‘main’:
strncat97.c:26:9: error: ‘strncat’ output truncated copying 15 bytes from a string of length 26 [-Werror=stringop-truncation]
(strncat)(buffer, data, len);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
$ clang -O3 -g -std=c11 -Wall -Wextra -Werror strncat97.c -o strncat97
$
Это демонстрирует преимущество использования более одного компилятора - разные компиляторы иногда обнаруживают разные проблемы. Этот код запутан, пытаясь использовать разное количество опций для выполнения нескольких задач. Достаточно показать:
$ ./strncat97
0x7ffee7506420: buffer 15: [ABCDEFGHIJKLMNO]
0x7ffee7506430: spare1 3: [abc]
0x7ffee7506410: spare2 3: [xyz]
$ ./strncat97 ABCDEFGHIJKLMNOP
Abort trap: 6
$ ./strncat97 ABCDEFGHIJKLMNO
0x7ffeea141410: buffer 15: [ABCDEFGHIJKLMNO]
0x7ffeea141420: spare1 3: [abc]
0x7ffeea141400: spare2 3: [xyz]
$
strncat37.c
Это пение, все танцы танцевальной версии программ, указанных выше, с опцией обработки через getopt()
. Он также использует мои процедуры сообщения об ошибках; код для них доступен в моем репозитории SOQ (Вопросы о переполнении стека) на GitHub в виде файлов stderr.c
и stderr.h
в подкаталоге src / libsoq .
#include "stderr.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*
** Demonstrating that strncat() should not be given sizeof(buffer) as
** the size, even if the string is empty to start with. The use of
** (strncat) inhibits the macro expansion on macOS; the code behaves
** differently when the __strncat_chk function (on High Sierra or
** earlier - it's __builtin__strncat_chk on Mojave) is called instead.
** You get an abort 6 (but no other useful message) when the buffer
** length is too long.
*/
static const char optstr[] = "fhlmsV";
static const char usestr[] = "[-fhlmsV] [string]";
static const char hlpstr[] =
" -f Function is called directly\n"
" -h Print this help message and exit\n"
" -l Long buffer length -- sizeof(buffer)\n"
" -m Macro cover for the function is used (default)\n"
" -s Short buffer length -- sizeof(buffer)-1 (default)\n"
" -V Print version information and exit\n"
;
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
int f_flag = 0;
int l_flag = 0;
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case 'f':
f_flag = 1;
break;
case 'h':
err_help(usestr, hlpstr);
/*NOTREACHED*/
case 'l':
l_flag = 1;
break;
case 'm':
f_flag = 0;
break;
case 's':
l_flag = 0;
break;
case 'V':
err_version(err_getarg0(), &"@(#)$Revision$ ($Date$)"[4]);
/*NOTREACHED*/
default:
err_usage(usestr);
/*NOTREACHED*/
}
}
if (optind < argc - 1)
err_usage(usestr);
const char *data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (optind != argc)
data = argv[optind];
char spare1[16] = "abc";
char buffer[16] = "";
char spare2[16] = "xyz";
size_t len = l_flag ? sizeof(buffer) : sizeof(buffer) - 1;
printf("Specified length: %zu\n", len);
printf("Copied string: [%s]\n", data);
printf("Copied %s\n", f_flag ? "using strncat() function directly"
: "using strncat() macro");
if (f_flag)
(strncat)(buffer, data, len);
else
strncat(buffer, data, len);
printf("%p: buffer %2zu: [%s]\n", (void *)buffer, strlen(buffer), buffer);
printf("%p: spare1 %2zu: [%s]\n", (void *)spare1, strlen(spare1), spare1);
printf("%p: spare2 %2zu: [%s]\n", (void *)spare2, strlen(spare2), spare2);
return 0;
}
Как и прежде, Clang и GCC имеют разные взгляды на приемлемость кода (и -Werror
означает, что предупреждение от GCC рассматривается как ошибка):
$ clang -O3 -g -I./inc -std=c11 -Wall -Wextra -Werror strncat37.c -o strncat37 -L./lib -lsoq
$ gcc -O3 -g -I./inc -std=c11 -Wall -Wextra -Werror strncat37.c -o strncat37 -L./lib -lsoq
strncat37.c: In function ‘main’:
strncat37.c:80:9: error: ‘strncat’ output may be truncated copying between 15 and 16 bytes from a string of length 26 [-Werror=stringop-truncation]
(strncat)(buffer, data, len);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
$
При запуске:
$ ./strncat37 -h
Usage: strncat37 [-fhlmsV] [string]
-f Function is called directly
-h Print this help message and exit
-l Long buffer length -- sizeof(buffer)
-m Macro cover for the function is used (default)
-s Short buffer length -- sizeof(buffer)-1 (default)
-V Print version information and exit
$ ./strncat37
Specified length: 15
Copied string: [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
Copied using strncat() macro
0x7ffedff4e400: buffer 15: [ABCDEFGHIJKLMNO]
0x7ffedff4e410: spare1 3: [abc]
0x7ffedff4e3f0: spare2 3: [xyz]
$ ./strncat37 -m -s
Specified length: 15
Copied string: [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
Copied using strncat() macro
0x7ffeeaf043f0: buffer 15: [ABCDEFGHIJKLMNO]
0x7ffeeaf04400: spare1 3: [abc]
0x7ffeeaf043e0: spare2 3: [xyz]
$ ./strncat37 -m -l
Specified length: 16
Copied string: [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
Copied using strncat() macro
Abort trap: 6
$ ./strncat37 -f -s
Specified length: 15
Copied string: [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
Copied using strncat() function directly
0x7ffeef0913f0: buffer 15: [ABCDEFGHIJKLMNO]
0x7ffeef091400: spare1 3: [abc]
0x7ffeef0913e0: spare2 3: [xyz]
$ ./strncat37 -f -l
Specified length: 16
Copied string: [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
Copied using strncat() function directly
0x7ffeed8d33f0: buffer 16: [ABCDEFGHIJKLMNOP]
0x7ffeed8d3400: spare1 0: []
0x7ffeed8d33e0: spare2 3: [xyz]
$
Поведение по умолчанию также является правильным поведением; программа не падает и не вызывает неожиданных побочных эффектов. При запуске с использованием макроса и с слишком большой указанной длиной (-m -l
) программа вылетает. При запуске с использованием функции и слишком большой длины (-f -l
) программа перезаписывает первый байт массива spare1
с добавленным нулем после конца buffer
и отображает 16 байтов данных вместо 15.
1 Одно исключение - scanf()
, когда вы используете %31s
или подобное; указанное число - это число ненулевых символов, которые могут быть сохранены в строке; он добавит нулевой байт после прочтения 31 других символов. Итак, опять же, максимальный размер, который можно безопасно использовать, составляет sizeof(string) - 1
.
Вы можете найти код для strncatXX.c
в моем SOQ (вопросы о переполнении стека) на GitHub в подкаталоге src / so-5405-4423 .
Анализ кода из вопроса
Извлечение кода из вопроса и изменение int main(){
на int main(void){
, потому что мои параметры компиляции по умолчанию генерируют ошибку (было бы предупреждением, если бы я не использовал -Werror
) для непрототипа main()
и добавление return 0;
в конце main()
, что осталось, дает мне эти ошибки компиляции с GCC 8.2.0 на Mac под управлением macOS 10.14.2 Mojave:
$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes so-5405-4423-v1.c -o so-5405-4423-v1
In file included from /opt/gcc/v8.2.0/lib/gcc/x86_64-apple-darwin17.7.0/8.2.0/include-fixed/stdio.h:425,
from so-5405-4423-v1.c:1:
so-5405-4423-v1.c: In function ‘main’:
so-5405-4423-v1.c:32:29: error: ‘%d’ directive writing between 1 and 2 bytes into a region of size between 1 and 100 [-Werror=format-overflow=]
sprintf(result, "%s%d, ", text, i); // Format the text and store it in result
^~~~~~~~
so-5405-4423-v1.c:32:29: note: directive argument in the range [0, 10]
so-5405-4423-v1.c:32:13: note: ‘__builtin___sprintf_chk’ output between 4 and 104 bytes into a destination of size 100
sprintf(result, "%s%d, ", text, i); // Format the text and store it in result
^~~~~~~
so-5405-4423-v1.c:37:29: error: ‘ ’ directive writing 1 byte into a region of size between 0 and 99 [-Werror=format-overflow=]
sprintf(result, "%s%d ", text, i); // Format the text and store it in result
^~~~~~~
so-5405-4423-v1.c:37:13: note: ‘__builtin___sprintf_chk’ output between 3 and 102 bytes into a destination of size 100
sprintf(result, "%s%d ", text, i); // Format the text and store it in result
^~~~~~~
cc1: all warnings being treated as errors
$
Компилятор отмечает, что text
- это строка, которая может содержать от 0 до 99 символов, поэтому теоретически она может вызвать переполнение при объединении с числом и ", "
(или " "
для одной итерации). Тот факт, что он инициализирован в "String No."
, означает, что нет риска переполнения, но вы можете уменьшить его, используя более короткую длину для text
- скажем 20
вместо 100
.
Я признаю, что это предупреждение, которое является относительно новым в GCC, не всегда так полезно, как все это (и это тот случай, когда код в порядке, но предупреждение все еще появляется). Я обычно делаю исправляю проблему, хотя бы потому, что она в настоящее время обнаруживается с моими опциями по умолчанию, а код не компилируется с какими-либо предупреждениями с -Werror
, и я не готов обходиться без этого уровня защиты , Я не использую clang
'-Weverything
параметр raw; это производит предупреждения, которые определенно контрпродуктивны (по крайней мере AFAIAC). Тем не менее, я противостою вариантам «все», которые не работают для меня. Если бы вариант -Wall
или -Wextra
был слишком болезненным, по какой-то причине я бы воспользовался им, но осторожно. Я бы пересмотрел уровень боли и постарался разобраться с симптомом.
У вас также есть цикл:
for(j = 0; j < 10; j++){ // Now loop to change the line
strcpy(lines[i], line); // Copy the line of text into each line of the array
fputs(lines[i], file); // Put each line into the file
}
К сожалению, когда этот цикл выполняется, i
равен 10
, что выходит за границы массива lines
. Это может привести к аварии. Предположительно, индекс должен быть j
вместо i
.
Вот инструментальная версия вашего кода (so-5405-4423-v2.c
):
#include <stdio.h>
#include <string.h>
char line[1001];
char lines[11][1001];
char info[100];
char *extra_info(char string_1[], char string_2[], char string_3[],
char string_4[], char string_5[]);
int main(void)
{
char result[100], text[20];
const char filename[] = "test.txt";
FILE *file;
strcpy(text, "String No.");
file = fopen(filename, "w+");
if (file == NULL)
{
fprintf(stderr, "Failed to open file '%s' for writing/update\n", filename);
return 1;
}
for (int i = 0; i < 10; i++)
{
if (i != 9)
sprintf(result, "%s%d, ", text, i);
else
sprintf(result, "%s%d ", text, i);
fprintf(stderr, "Iteration %d:\n", i);
fprintf(stderr, "1 result (%4zu): [%s]\n", strlen(result), result);
fprintf(stderr, "1 line (%4zu): [%s]\n", strlen(line), line);
extra_info("st", "nd", "rd", "th", "th");
fprintf(stderr, "2 line (%4zu): [%s]\n", strlen(line), line);
fprintf(stderr, "1 info (%4zu): [%s]\n", strlen(info), info);
strncat(line, info, 100);
fprintf(stderr, "3 line (%4zu): [%s]\n", strlen(line), line);
printf("%s", result);
strncat(line, result, 15);
fprintf(stderr, "3 line (%4zu): [%s]\n", strlen(line), line);
}
fprintf(stderr, "4 line (%4zu): [%s]\n", strlen(line), line);
strncat(line, "\n\n", 2);
for (int j = 0; j < 10; j++)
{
strcpy(lines[j], line);
fputs(lines[j], file);
}
fclose(file);
return 0;
}
char *extra_info(char string_1[], char string_2[], char string_3[],
char string_4[], char string_5[])
{
char text[100];
sprintf(text, " 1%s", string_1);
fprintf(stderr, "EI 1: add (%zu) [%s] to (%zu) [%s]\n", strlen(string_1), string_1, strlen(line), line);
strncat(line, text, 100);
sprintf(text, ", 2%s", string_2);
fprintf(stderr, "EI 2: add (%zu) [%s] to (%zu) [%s]\n", strlen(string_2), string_2, strlen(line), line);
strncat(line, text, 100);
sprintf(text, ", 3%s", string_3);
fprintf(stderr, "EI 3: add (%zu) [%s] to (%zu) [%s]\n", strlen(string_3), string_3, strlen(line), line);
strncat(line, text, 100);
sprintf(text, ", 4%s", string_4);
fprintf(stderr, "EI 4: add (%zu) [%s] to (%zu) [%s]\n", strlen(string_4), string_4, strlen(line), line);
strncat(line, text, 100);
sprintf(text, ", 5%s.", string_5);
fprintf(stderr, "EI 5: add (%zu) [%s] to (%zu) [%s]\n", strlen(string_5), string_5, strlen(line), line);
strncat(line, text, 100);
fprintf(stderr, "EI 6: copy (%zu) [%s] to info\n", strlen(line), line);
strcpy(info, line);
return line;
}
При запуске выдает результат, похожий на:
Iteration 0:
1 result ( 13): [String No.0, ]
1 line ( 0): []
EI 1: add (2) [st] to (0) []
EI 2: add (2) [nd] to (4) [ 1st]
EI 3: add (2) [rd] to (9) [ 1st, 2nd]
EI 4: add (2) [th] to (14) [ 1st, 2nd, 3rd]
EI 5: add (2) [th] to (19) [ 1st, 2nd, 3rd, 4th]
EI 6: copy (25) [ 1st, 2nd, 3rd, 4th, 5th.] to info
2 line ( 25): [ 1st, 2nd, 3rd, 4th, 5th.]
1 info ( 25): [ 1st, 2nd, 3rd, 4th, 5th.]
3 line ( 50): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.]
3 line ( 63): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, ]
Iteration 1:
1 result ( 13): [String No.1, ]
1 line ( 63): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, ]
EI 1: add (2) [st] to (63) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, ]
EI 2: add (2) [nd] to (67) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st]
EI 3: add (2) [rd] to (72) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd]
EI 4: add (2) [th] to (77) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd]
EI 5: add (2) [th] to (82) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th]
EI 6: copy (88) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.] to info
2 line ( 88): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.]
1 info ( 88): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.]
3 line ( 176): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.]
3 line ( 189): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, ]
Iteration 2:
1 result ( 13): [String No.2, ]
1 line ( 189): [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, ]
EI 1: add (2) [st] to (189) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, ]
EI 2: add (2) [nd] to (193) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, 1st]
EI 3: add (2) [rd] to (198) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, 1st, 2nd]
EI 4: add (2) [th] to (203) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, 1st, 2nd, 3rd]
EI 5: add (2) [th] to (208) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, 1st, 2nd, 3rd, 4th]
EI 6: copy (214) [ 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th. 1st, 2nd, 3rd, 4th, 5th.String No.0, 1st, 2nd, 3rd, 4th, 5th.String No.1, 1st, 2nd, 3rd, 4th, 5th.] to info
String No.0, String No.1, Abort trap: 6
Когда вы замечаете, что 214 байтов копируются из line
(который достаточно велик, чтобы вместить эту строку) в info
(который не - это всего лишь 100 байт), последующий сбой не очень удивителен. Не совсем понятно, каково желаемое поведение.
На моем Mac отладчик lldb
сообщает о сбое в __strcpy_chk
; AFAICT, это в выделенной строке в конце extra_info()
:
frame #6: 0x00007fff681bbe84 libsystem_c.dylib`__strcpy_chk + 83
frame #7: 0x00000001000017cc so-5405-4423-v2`extra_info(string_1=<unavailable>, string_2=<unavailable>, string_3="rd", string_4="th", string_5="th") at so-5405-4423-v2.c:86
Итак, хотя это, очевидно, не strncat()
, что вызывает сбой здесь, способ, которым strncat()
используется , явно не корректен - IMO, это неверно, но представления могут отличаться. И я все еще придерживаюсь своего основного заключения: Не используйте strncat()
.