简体   繁体   English

如何使用strncat()在运行时避免Abort Trap 6错误?

[英]How To Avoid Abort Trap 6 Error at Runtime Using strncat()?

The Abort trap 6 issue is stemming from calling the extra_info() method, where it uses strncat() multiple times. 中止陷阱6的问题源于调用extra_info()方法,该方法多次使用strncat()。 Removing this functionality would produce no errors at runtime. 删除此功能将不会在运行时产生任何错误。

From what I understood: 据我了解:

Abort trap: 6 is caused by using invalid indices pointing to non-existent memory locations Abort trap: 6 in C Program . 中止陷阱:6是由于使用指向不存在的内存位置的无效索引导致的。 中止陷阱:6在C Program中 It may also occur when a variable memory needs to be freed. 当需要释放可变内存时,也可能发生这种情况。 To avoid this scenario, you can use multiple variables or free the single variable every time it is be re-used. 为了避免这种情况,您可以使用多个变量,也可以在每次重用时释放单个变量。 But I'm sensing the solution is much simpler. 但是我感觉到解决方案要简单得多。

#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;

}

This code compiles nicely using GCC, but I've stumbled upon cases where the code works fine, nevertheless may ruin certain functionality because of this error. 该代码可以使用GCC很好地编译,但是我偶然发现了代码可以正常工作的情况,但是由于该错误可能会破坏某些功能。 This has something to do with strncat() being called multiple times in this manner, which leads me into thinking there would be a memory allocation issue, but after trying other examples, the solution seems much simpler. 这与以这种方式多次调用strncat()有关,这使我想到会有内存分配问题,但是在尝试其他示例之后,解决方案似乎简单得多。 Any help on this will be appreciated. 任何帮助,将不胜感激。 Thanks in advance. 提前致谢。

I wrote the accompanying code in March 2018 to satisfy myself about what goes on with strncat() for another question that got deleted before I submitted an answer. 我在2018年3月编写了随附的代码,以使自己对strncat()的问题感到满意,因为在提交答案之前删除了另一个问题。 This is just retargeting that code. 这只是重新定位该代码。

The strncat() function is (as I said in a comment ) evil and vile. 正如我在评论中所说, strncat()函数是邪恶的。 It is inconsistent with the strncpy() interface, too — and different from anything you'll encounter anywhere else. 它也与strncpy()接口不一致-与您在其他任何地方遇到的任何东西都不一样。 After reading this, you will decide (with luck) that you should never use strncat() . 阅读此内容后,您将(幸运的是)决定永远不要使用strncat()

TL;DR — Never use strncat() TL; DR —永远不要使用strncat()

The C standard defines strncat() (and POSIX agrees — strncat() ) C标准定义了strncat() (并且POSIX同意— strncat()

C11 §7.24.3.2 The strncat function C11§7.24.3.2的strncat功能

Synopsis 概要

 #include <string.h> char *strncat(char * restrict s1, const char * restrict s2, size_t n); 

Description 描述

The strncat function appends not more than n characters (a null character and characters that follow it are not appended) from the array pointed to by s2 to the end of the string pointed to by s1 . s2指向的数组到s1指向的字符串的末尾, strncat函数将不超过n字符(null字符和不跟随strncat字符附加)。 The initial character of s2 overwrites the null character at the end of s1 . s2的初始字符覆盖s1末尾的空字符。 A terminating null character is always appended to the result. 总是将终止的空字符附加到结果上。 309) If copying takes place between objects that overlap, the behavior is undefined. 309)如果复制发生在重叠的对象之间,则行为是不确定的。

Returns 退货

The strncat function returns the value of s1 . strncat函数返回s1的值。

309) Thus, the maximum number of characters that can end up in the array pointed to by s1 is strlen(s1)+n+1 . 309)因此,在s1指向的数组中可以出现的最大字符数为strlen(s1)+n+1

The footnote identifies the biggest trap with strncat() — you can't safely use: 该脚注使用strncat()标识了最大的陷阱—您不能安全地使用:

char *source = …;

char target[100] = "";

strncat(target, source, sizeof(target));

This is contrary to what occurs with most other functions that take an array size argument 1 in C code. 这与大多数其他函数在C代码中采用数组大小​​参数1的情况相反。

To safely use strncat() , you should know: 为了安全地使用strncat() ,您应该知道:

  • target
  • sizeof(target) — or, for dynamically allocated space, the allocated length sizeof(target) —或者,对于动态分配的空间,分配的长度
  • strlen(target) — you must know the length of what is already in the target string strlen(target) -您必须知道目标字符串中已经存在的长度
  • source
  • strlen(source) — if you are concerned about whether the source string was truncated; strlen(source) —如果您担心源字符串是否被截断; not needed if you don't care 如果您不在乎,则不需要

With that information, you could use: 有了这些信息,您可以使用:

strncat(target, source, sizeof(target) - strlen(target) - 1);

However, doing that would be a little silly; 但是,这样做有点愚蠢。 if you know strlen(target) , you can avoid making strncat() find it out again by using: 如果您知道strlen(target) ,则可以避免使用以下命令让strncat()再次找到它:

strncat(target + strlen(target), source, sizeof(target) - strlen(target) - 1);

Note that strncat() guarantees null termination, unlike strncpy() . 请注意,与strncpy()不同, strncat()保证了空终止。 That means that you could use: 这意味着您可以使用:

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';

and you would be guaranteed the same result if the source string is too long to be appended to the target. 如果源字符串太长而无法追加到目标,则可以保证得到相同的结果。

Demo Code 示范代码

Multiple programs that illustrate aspects of strncat() . 多个程序,说明了strncat()各个方面。 Note that on macOS, there is a macro definition of strncat() in <string.h> which invokes a different function — __builtin___strncat_chk — which validates the uses of strncat() . 请注意,在macOS上, <string.h>strncat()的宏定义,该宏定义调用另一个函数__builtin___strncat_chk ,该函数验证strncat()的使用。 For compactness of the command lines, I've dropped two warning compiler options that I normally use — -Wmissing-prototypes -Wstrict-prototypes — but that doesn't affect any of the compilations. 为了-Wmissing-prototypes -Wstrict-prototypes命令行,我删除了通常使用的两个警告编译器选项-Wmissing-prototypes -Wstrict-prototypes但这并不影响任何编译。

strncat19.c

This demonstrates one safe use of strncat() : 这说明了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;
}

It compiles cleanly (with Apple's clang from XCode 10.1 ( Apple LLVM version 10.0.0 (clang-1000.11.45.5) ) and GCC 8.2.0, even with stringent warnings set: 它编译干净(与苹果公司的clang从10.1的XCode( 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

This is similar to strncat19.c but (a) allows you to specify a string to be copied on the command line, and (b) incorrectly uses sizeof(buffer) instead of sizeof(buffer) - 1 for the length. 这类似于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;
}

This code doesn't compile with the stringent warning options: 此代码无法使用严格的警告选项进行编译:

$ 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
$

Even with no warnings requested, the warning is given by GCC, but because the -Werror option is absent, it produces an executable: 即使没有请求警告,警告也是由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
$

That is the __builtin__strncat_chk function at work. 那就是正在使用的__builtin__strncat_chk函数。

strncat97.c

This code also takes an optional string argument; 该代码还带有一个可选的字符串参数。 it also pays attention to whether there is another argument on the command line, and if so, it invokes the strncat() function directly, rather than letting the macro check it first: 它还注意命令行上是否还有另一个参数,如果是,它会直接调用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;
}

Now the compilers produce different results: 现在,编译器产生不同的结果:

$ 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  
$

This demonstrates an advantage of using more than one compiler — different compilers detect different problems on occasion. 这证明了使用多个编译器的优势-不同的编译器有时会检测到不同的问题。 This code is messy trying to used different numbers of options to do multiple things. 尝试使用不同数量的选项来执行多项操作,此代码很混乱。 It suffices to show: 足以显示:

$ ./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

This is the all-singing, all-dancing version of the programs above, with option handling via getopt() . 这是上面程序的全程跳舞的版本,通过getopt()处理选项。 It also uses my error reporting routines; 它还使用我的错误报告例程; the code for them is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory. 它们的代码可在GitHub上的我的SOQ (堆栈溢出问题)存储库中以src / libsoq子目录中的stderr.cstderr.h文件的stderr.c stderr.h

#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;
}

As before, Clang and GCC have different views on the acceptability of the code (and -Werror means the warning from GCC is treated as an error): 和以前一样,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
$

When run: 运行时:

$ ./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]
$

The default behaviour is also the correct behaviour; 默认行为也是正确的行为。 the program doesn't crash and doesn't produce unexpected side-effects. 该程序不会崩溃,也不会产生意外的副作用。 When run using the macro and with too long a length specified ( -m -l ), the program crashes. 当使用宏运行并且指定的长度过长( -m -l )时,程序崩溃。 When run using the function and too long a length ( -f -l ), the program overwrites the first byte of array spare1 with the null added after the end of buffer , and shows 16 bytes of data instead of 15. 当使用该函数运行并且长度太长( -f -l )时,程序将在buffer末尾之后添加null来覆盖数组spare1的第一个字节,并显示16个字节的数据,而不是15个字节。


1 One exception is in scanf() when you use %31s or similar; 1当您使用%31s或类似的东西时, scanf()就是一个例外。 the number specified is the number of non-null characters that can be stored in the string; 指定的数字是可以存储在字符串中的非空字符的数目; it will add a null byte after reading 31 other characters. 读取其他31个字符后,它将添加一个空字节。 So again, the maximum size that can be used safely is sizeof(string) - 1 . 同样,可以安全使用的最大大小为sizeof(string) - 1

You can find the code for strncatXX.c in my SOQ (Stack Overflow Questions) repository on GitHub in the src/so-5405-4423 sub-directory. 您可以在src / so-5405-4423子目录中的GitHub上的SOQ (堆栈溢出问题)存储库中找到strncatXX.c的代码。


Analysis of Code from Question 问题代码分析

Taking the code from the question and changing int main(){ to int main(void){ because my default compilation options generate an error (it would be a warning if I didn't use -Werror ) for the non-prototype main() , and adding return 0; 从问题中获取代码并将int main(){更改为int main(void){因为我的默认编译选项对非原型main() )产生了一个错误(如果我不使用-Werror这将是一个警告) main() ,并加上return 0; at the end of main() , what's left gives me these errors compiling with GCC 8.2.0 on a Mac running macOS 10.14.2 Mojave: main()的末尾,剩下的让我在运行macOS 10.14.2 Mojave的Mac上使用GCC 8.2.0编译时出现以下错误:

$ 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
$

The compiler notes that text is a string that can contain 0 to 99 characters, so it could in theory cause an overflow when concatenated with a number and the ", " (or the " " for one iteration). 编译器注意到text是一个可以包含0到99个字符的字符串,因此从理论上讲,如果将其与数字和", " (或" "进行一次迭代)连接,则可能导致溢出。 The fact that it is initialized to "String No." 它被初始化为"String No."的事实。 means that there isn't an overflow risk, but you can mitigate that by using a shorter length for text — say 20 instead of 100 . 表示没有溢出风险,但是您可以通过使用较短的text长度(例如20而不是100来减轻这种情况。

I admit that this warning, which is relatively new in GCC, is not always as helpful as all that (and this is a case where the code is OK, but the warning still appears). 我承认这个警告在GCC中是相对较新的,并不总是那么有用(这是代码正常的情况,但是警告仍然出现)。 I usually do fix the problem, if only because it currently shows up with my default options and code doesn't compile with any warnings with -Werror and I'm not ready to do without that level of protection. 我通常解决此问题,仅是因为它当前会以我的默认选项显示,并且代码无法通过-Werror进行任何警告编译,而且如果没有该级别的保护,我也无法做好准备。 I don't use clang 's -Weverything option raw; 我不使用clang-Weverything选项raw; it produces warnings which are definitely counter-productive (at least AFAIAC). 它会产生绝对适得其反的警告(至少是AFAIAC)。 However, I countermand the 'everything' options that don't work for me. 但是,我反对对我不起作用的“所有”选项。 If a -Wall or -Wextra option was too painful, for some reason, I'd countermand it, but cautiously. 如果-Wall-Wextra选项由于某种原因太痛苦,我会-Wextra ,但要谨慎。 I'd review the pain level, and aim to deal with whatever the symptom is. 我会回顾疼痛的程度,并致力于解决任何症状。

You also have the loop: 您也有循环:

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        

}   

Unfortunately, when this loop runs, i is equal to 10 , which is out of bounds of the array lines . 不幸的是,当此循环运行时, i等于10 ,这超出了数组lines的范围。 This can lead to a crash. 这可能导致崩溃。 Presumably, the index should be j instead of i . 大概索引应该是j而不是i

Here's an instrumented version of your code ( so-5405-4423-v2.c ): 这是代码的检测版本( 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;
}

When run, it produces output similar to: 运行时,其输出类似于:

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

When you observe that 214 bytes are copied from line (which is big enough to hold that string) to info (which is not — it is but 100 bytes long), the ensuing crash is not very surprising. 当您观察到从line (足够大以容纳该字符串)将214个字节复制到info (不是-而是100个字节长)时,随之而来的崩溃并不是很令人惊讶。 It isn't entirely clear what the desired behaviour is. 尚不清楚所需的行为是什么。

On my Mac, the lldb debugger reports the crash in __strcpy_chk ; 在我的Mac上, lldb调试器在__strcpy_chk报告崩溃; AFAICT, it's in the line highlighted at the end of extra_info() : 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

So, while it apparently isn't strncat() that causes the crash here, the way that strncat() is used is not obviously correct — IMO, it is incorrect, but views may differ. 所以,虽然它显然不是strncat()导致崩溃这里,该方法strncat() 使用不显然是正确-国际海事组织,这是不正确的,但看法可能会有所不同。 And I still stand by my basic conclusion: Do not use strncat() . 而且我仍然坚持我的基本结论: 不要使用strncat()

The solution was simple as I was sensing, nothing evil, vile or cynical in C at all. 当我感觉到时,解决方案很简单,在C语言中根本没有邪恶,邪恶或愤世嫉俗的现象。 1st of all strcpy() didn't have to happen, 2nd extra_info() was misplaced for its purpose, 3rd even if I were to use strcpy() the parameters are to be swapped. 所有strcpy()的第1个都不需要发生,第2个extra_info()出于其目的而被放错了位置,第3个即使我要使用strcpy()来交换参数也是如此。 Hence the error Abort trap 6 : 因此,错误中止陷阱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;

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM