Как избежать ошибки прерывания Trap 6 во время выполнения с помощью strncat ()? - PullRequest
0 голосов
/ 05 января 2019

Проблема Abort trap 6 связана с вызовом метода extra_info (), где он многократно использует strncat (). Удаление этой функции не приведет к ошибкам во время выполнения.

Из того, что я понял:

Отмена прерывания: 6 вызвано использованием недопустимые индексы, указывающие на несуществующие области памяти Прервать прерывание: 6 в программе C . Это может также произойти, когда переменная память должна быть освобождена. Избежать В этом сценарии вы можете использовать несколько переменных или освободить один переменная каждый раз, когда она будет использоваться повторно. Но я чувствую, что решение намного проще.

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

char line[1001]; // The line supports up to a 1000 characters
char lines[11][1001]; // An array of lines (up to 10 lines where each line is a 1000 characters max)
char info[100]; // Holds extra info provided by user

char * extra_info(
        char string_1[],
        char string_2[],
        char string_3[],
        char string_4[],
        char string_5[]
    );

int main(){

    int 
    i, // Line number
    j; // Length of the line
    char result[100], text[100];
    FILE *file;

    strcpy(text, "String No."); // The default text

    file = fopen("test.txt", "w+"); // Open the file for reading and writing

    for(i = 0; i < 10; i++){ // Loop to create a line.

        if(i != 9){ // If the line is NOT at the 10th string

            sprintf(result, "%s%d, ", text, i); // Format the text and store it in result

        }
        else{

            sprintf(result, "%s%d ", text, i); // Format the text and store it in result            

        }

        extra_info(
            "st",
            "nd",
            "rd",
            "th",
            "th"
        );

        strncat(line, info, 100); // Append the extra info at the end of each line        

        printf("%s", result); // Display the result variable to the screen

        strncat(line, result, 15); // Concatenate all strings in one line

    }

    strncat(line, "\n\n", 2); // Add a new-line character at the end of each line

    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        

    }

    fclose(file);  

}

char * extra_info( // Append user defined and predefined info at the end of a line
        char string_1[],
        char string_2[],
        char string_3[],
        char string_4[],
        char string_5[]
    ){
        char text[100]; // A variable to hold the text

        /* Append a default text into each strings 
        and concatenate them into one line */

        sprintf(text, " 1%s", string_1);
        strncat(line, text, 100);

        sprintf(text, ", 2%s", string_2);
        strncat(line, text, 100);

        sprintf(text, ", 3%s", string_3);
        strncat(line, text, 100);

        sprintf(text, ", 4%s", string_4);
        strncat(line, text, 100);

        sprintf(text, ", 5%s.", string_5);
        strncat(line, text, 100);

        strcpy(info, line); // Copies the line into the info global variable

        return line;

}

Этот код прекрасно компилируется с использованием GCC, но я сталкивался со случаями, когда код работает нормально, но, тем не менее, может разрушить некоторые функции из-за этой ошибки. Это как-то связано с тем, что strncat () вызывается таким образом несколько раз, что наводит меня на мысль, что возникнет проблема с выделением памяти, но, попробовав другие примеры, решение кажется намного проще. Любая помощь в этом будет оценена. Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 06 января 2019

Я написал сопровождающий код в марте 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().

0 голосов
/ 06 января 2019

Решение было простым, поскольку я чувствовал, ничего злого, мерзкого или циничного в Си вообще. 1-й из всех strcpy () не должен был произойти, 2-й extra_info () был неуместен для своей цели, 3-й, даже если бы я использовал strcpy (), параметры нужно поменять местами. Отсюда ошибка Прервать ловушку 6 :

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

char line[1001]; // The line supports up to a 1000 characters
char lines[11][1001]; // An array of lines (up to 10 lines where each line is a 1000 characters max)
char info[100]; // Holds extra info provided by user

char * extra_info(
        char string_1[],
        char string_2[],
        char string_3[],
        char string_4[],
        char string_5[]
    );

int main(){

    int 
    i, // Line number
    j; // Length of the line
    char result[100], text[100];
    FILE *file;

    strcpy(text, "String No."); // The default text

    file = fopen("test.txt", "w+"); // Open the file for reading and writing

    for(i = 0; i < 10; i++){ // Loop to create a line.

        if(i != 9){ // If the line is NOT at the 10th string

            sprintf(result, "%s%d, ", text, i); // Format the text and store it in result

        }
        else{

            sprintf(result, "%s%d ", text, i); // Format the text and store it in result            

        }

        strncat(line, info, 100); // Append the extra info at the end of each line        

        strncat(line, result, 15); // Concatenate all strings in one line

    }

    extra_info(
        "st",
        "nd",
        "rd",
        "th",
        "th"
    );    

    strncat(line, "\n\n", 2); // Add a new-line character at the end of each line

    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        

    }

    fclose(file);  

}

char * extra_info( // Append user defined and predefined info at the end of a line
        char string_1[],
        char string_2[],
        char string_3[],
        char string_4[],
        char string_5[]
    ){
        char text[100]; // A variable to hold the text

        /* Append a default text into each strings 
        and concatenate them into one line */

        sprintf(text, " 1%s", string_1);
        strncat(line, text, 100);

        sprintf(text, ", 2%s", string_2);
        strncat(line, text, 100);

        sprintf(text, ", 3%s", string_3);
        strncat(line, text, 100);

        sprintf(text, ", 4%s", string_4);
        strncat(line, text, 100);

        sprintf(text, ", 5%s.", string_5);
        strncat(line, text, 100);

        return line;

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...