我在 2018 年 3 月编写了随附的代码,以使自己对 strncat() 的情况感到满意,该问题在我提交答案之前已被删除。这只是重新定位该代码。
strncat() 函数(正如我在comment 中所说)邪恶而卑鄙。它也与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 函数从s2 指向的数组中追加不超过n 个字符(不追加空字符和后面的字符)到s1 指向的字符串末尾. s2 的初始字符覆盖s1 末尾的空字符。终止的空字符始终附加到结果中。309) 如果复制发生在重叠的对象之间,则行为未定义。
退货
strncat 函数返回s1 的值。
309) 因此,s1 指向的数组中可以包含的最大字符数为strlen(s1)+n+1。
脚注用strncat()标识了最大的陷阱——你不能安全地使用:
char *source = …;
char target[100] = "";
strncat(target, source, sizeof(target));
这与在 C 代码中采用数组大小参数 1 的大多数其他函数的情况相反。
要安全使用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 上,<string.h> 中有一个 strncat() 的宏定义,它调用了一个不同的函数——__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;
}
它编译干净(使用来自 XCode 10.1 (Apple LLVM version 10.0.0 (clang-1000.11.45.5)) 和 GCC 8.2.0 的 Apple 的 clang,即使设置了严格的警告:
$ 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;
}
现在编译器会产生不同的结果:
$ 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() 处理选项。它还使用我的错误报告程序;它们的代码可以在我在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 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 当您使用%31s 或类似名称时,scanf() 中的一个例外情况;指定的数字是可以存储在字符串中的非空字符数;它会在读取 31 个其他字符后添加一个空字节。同样,可以安全使用的最大大小是sizeof(string) - 1。
您可以在我的SOQ(堆栈溢出问题)存储库中的src/so-5405-4423 子目录中找到strncatXX.c 的代码。
问题代码分析
从问题中获取代码并将int main(){ 更改为int main(void){,因为我的默认编译选项会为非原型main() 生成错误(如果我不使用-Werror,这将是一个警告) ,并在main() 的末尾添加return 0;,剩下的就是在运行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
$
编译器注意到 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()。