在 C 中连接 const 字符串和变量输入
concatenate const strings and variable inputs in C
我在用 C 语言工作,我必须连接一些东西。
现在我有这个:
int main(int argc, char *argv[])
{
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
char ip_server[IP_MAX_LEN];
char file_name[FILE_MAX_LEN];
const char * tftp_get = "tftp -g -r ";
strcpy(&ip_server[0], argv[1]);
strcpy(&file_name[0], argv[2]);
tftp_cmd[0] = '[=10=]';
strcpy(tftp_cmd, tftp_get);
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, file_name, sizeof(tftp_get) + sizeof(file_name));
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, ip_server, sizeof(tftp_get) + sizeof(file_name) + 1);
printf("tftp get command = %s\n", tftp_cmd);
return 0
}
此应用 returns:
# ./test_app 10.0.0.1 MY_TEST_FILE_17.12.2015
tftp get command = tftp -g -r
tftp get command = tftp -g -r MY_TEST_FILE_17.12.2015
tftp get command = tftp -g -r MY_TEST_FILE_17.12.201510.0.0.1
我要tftp -g -r MY_TEST_FILE_17.12.2015 10.0.0.1
我用的好方法吗?
首先,请注意 &ip_server[0]
与 ip_server
本质上相同,其次 sizeof
并不像您显然认为的那样。
这里
strncat(tftp_cmd, file_name, sizeof(tftp_get) + sizeof(file_name));
相反,做类似的事情
strcat(tftp_cmd, file_name);
tftp_cmd
必须指向足够大的内存缓冲区。
使用 strncat()
可能非常危险,因为它可能会遗漏终止符 '[=18=]'
.
您也不需要初始化 tftp_cmd
,因为它是 strcpy()
的第一个参数,但您需要它指向足够大的内存缓冲区。而且你根本不需要复制argv[1]
和argv[2]
,你可以直接使用它们。如果必须,您可以使用指针而不是昂贵的复制内存。
最后,按照@EugeneSh. in this 的建议,最好的办法是snprintf()
,这是一个例子
int main(int argc, char *argv[])
{
char *tftp_cmd;
ssize_t length;
const char *format;
format = "tftp -g -r %s %s";
if (argc < 3) // Check that parameters were passed to the funcion
return -1;
length = snprintf(NULL, 0, format, argv[2], argv[1]);
tftp_cmd = malloc(length + 1);
if (tftp_cmd == NULL)
return -1; // Allocation Error
sprintf(tftp_cmd, format, argv[2], argv[1]);
// Use the `tftp_cmd' here, for example
fprintf(stdout, "%s\n", tftp_cmd);
// And then, free it
free(tftp_cmd);
return 0;
}
显然,所提供的代码存在各种问题:
- 参数被不必要地复制到临时缓冲区中,没有检查缓冲区溢出。 (例如
strcpy(&ip_server[0], argv[1]);
)
strncat
的第三个参数是第二个参数的最大长度,而不是连接字符串的最大长度。示例代码中提供的值允许缓冲区溢出。
- IP 号前没有插入所需的 space 字符。
- 过度依赖固定大小的数组。
更重要的是,通用方法既难读又低效。 C 中的字符串处理有点烦人,但自 1989 年以来,C 字符串库有了重大改进,使用新的库功能可以使您的代码更安全、更易读,甚至更高效。好的 C 代码应该充分利用可用的库功能。
(在原始代码中重复使用 strncat
是低效的,因为 strncat
会在每次调用时从头开始重新扫描输出字符串,随着字符串变长,导致执行时间成二次方.)
更好的方法是使用 snprintf
提供简单、易读、安全和高效的格式化操作:
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Too few arguments.\n");
exit(1);
}
/* These are for documentation; no copying is involved */
const char* file_name = argv[2];
const char* ip_server = argv[1];
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
int outlen = snprintf(tftp_cmd, sizeof tftp_cmd,
"tftp -g -r %s %s", file_name, ip_server);
if (outlen >= sizeof tftp_cmd) {
fprintf(stderr, "Arguments are too long\n");
exit(1);
}
printf("%s\n", tftp_cmd);
return 0;
}
这可以通过分配内存而不是使用固定大小的缓冲区来改进:
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Too few arguments.\n");
exit(1);
}
char* tftp_cmd = NULL;
const char* file_name = argv[2];
const char* ip_server = argv[1];
int outlen = snprintf(tftp_cmd, 0,
"tftp -g -r %s %s", file_name, ip_server);
tftp_cmd = malloc(outlen + 1);
snprintf(tftp_cmd, outlen + 1,
"tftp -g -r %s %s", file_name, ip_server);
printf("%s\n", tftp_cmd);
free(tftp_cmd);
return 0;
}
一些现代 C 库实现了 asprintf
,它会自动分配内存,而且更加方便,因为它避免了调用 snprintf
两次。
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Too few arguments.\n");
exit(1);
}
char* tftp_cmd = NULL;
const char* file_name = argv[2];
const char* ip_server = argv[1];
if (0 > asprintf(&tftp_cmd,
"tftp -g -r %s %s", file_name, ip_server)) {
fprintf(stderr, "Memory allocation error\n");
exit(1);
}
printf("%s\n", tftp_cmd);
free(tftp_cmd);
return 0;
}
以下代码更正了发布的代码中发现的问题。
int main(int argc, char *argv[])
{
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
char ip_server[IP_MAX_LEN];
char file_name[FILE_MAX_LEN];
const char * tftp_get = "tftp -g -r ";
if( 3 != argc )
{
fprintf( stderr, "USAGE: %s <serverIP> <filename>\n", argv[0]);
exit( EXIT_FAILURE );
}
// implied else, correct number of command line parameters
strcpy(ip_server, argv[1]);
strcpy(file_name, argv[2]);
// tftp_cmd[0] = '[=10=]'; -- not needed because first data is set by strcpy()
strcpy(tftp_cmd, tftp_get);
printf("tftp get command = %s\n", tftp_cmd);
strcat(tftp_cmd, file_name);
printf("tftp get command = %s\n", tftp_cmd);
strcat(tftp_cmd, ip_server );
printf("tftp get command = %s\n", tftp_cmd);
return 0
}
然而,对 strcat()
的一系列调用可能(可能)溢出 tftp_cmd[]
缓冲区。并且发布的代码正在尝试使用 strncat()
因此以下代码更安全,因为它不会溢出 tftp_cmd[] 缓冲区。
int main(int argc, char *argv[])
{
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
char ip_server[IP_MAX_LEN];
char file_name[FILE_MAX_LEN];
const char * tftp_get = "tftp -g -r ";
if( 3 != argc )
{
fprintf( stderr, "USAGE: %s <serverIP> <filename>\n", argv[0]);
exit( EXIT_FAILURE );
}
// implied else, correct number of command line parameters
strcpy(ip_server, argv[1]);
strcpy(file_name, argv[2]);
// tftp_cmd[0] = '[=11=]'; -- not needed because first data is set by strcpy()
strncpy(tftp_cmd, tftp_get, TFTP_MAX_BUFFER_SIZE);
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, file_name. TFTP_MAX_BUFFER_SIZE - strlen( tftp_cmd ) );
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, ip_server, TFTP_MAX_BUFFER_SIZE - strlen( tftp_cmd );
printf("tftp get command = %s\n", tftp_cmd);
return 0
}
然而,这并没有提供任何迹象表明缓冲区实际上包含整个数据字符串。
所以可以在 return
语句
之前插入类似下面的内容
if( (strlen( tftp_get) + strlen( file_name ) + strlen( ip_server ) +1 ) > TFTP_MAX_BUFFER_SIZE )
{
printf( "unable to create the full contents of the tftp command\n" );
}
我在用 C 语言工作,我必须连接一些东西。
现在我有这个:
int main(int argc, char *argv[])
{
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
char ip_server[IP_MAX_LEN];
char file_name[FILE_MAX_LEN];
const char * tftp_get = "tftp -g -r ";
strcpy(&ip_server[0], argv[1]);
strcpy(&file_name[0], argv[2]);
tftp_cmd[0] = '[=10=]';
strcpy(tftp_cmd, tftp_get);
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, file_name, sizeof(tftp_get) + sizeof(file_name));
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, ip_server, sizeof(tftp_get) + sizeof(file_name) + 1);
printf("tftp get command = %s\n", tftp_cmd);
return 0
}
此应用 returns:
# ./test_app 10.0.0.1 MY_TEST_FILE_17.12.2015
tftp get command = tftp -g -r
tftp get command = tftp -g -r MY_TEST_FILE_17.12.2015
tftp get command = tftp -g -r MY_TEST_FILE_17.12.201510.0.0.1
我要tftp -g -r MY_TEST_FILE_17.12.2015 10.0.0.1
我用的好方法吗?
首先,请注意 &ip_server[0]
与 ip_server
本质上相同,其次 sizeof
并不像您显然认为的那样。
这里
strncat(tftp_cmd, file_name, sizeof(tftp_get) + sizeof(file_name));
相反,做类似的事情
strcat(tftp_cmd, file_name);
tftp_cmd
必须指向足够大的内存缓冲区。
使用 strncat()
可能非常危险,因为它可能会遗漏终止符 '[=18=]'
.
您也不需要初始化 tftp_cmd
,因为它是 strcpy()
的第一个参数,但您需要它指向足够大的内存缓冲区。而且你根本不需要复制argv[1]
和argv[2]
,你可以直接使用它们。如果必须,您可以使用指针而不是昂贵的复制内存。
最后,按照@EugeneSh. in this snprintf()
,这是一个例子
int main(int argc, char *argv[])
{
char *tftp_cmd;
ssize_t length;
const char *format;
format = "tftp -g -r %s %s";
if (argc < 3) // Check that parameters were passed to the funcion
return -1;
length = snprintf(NULL, 0, format, argv[2], argv[1]);
tftp_cmd = malloc(length + 1);
if (tftp_cmd == NULL)
return -1; // Allocation Error
sprintf(tftp_cmd, format, argv[2], argv[1]);
// Use the `tftp_cmd' here, for example
fprintf(stdout, "%s\n", tftp_cmd);
// And then, free it
free(tftp_cmd);
return 0;
}
显然,所提供的代码存在各种问题:
- 参数被不必要地复制到临时缓冲区中,没有检查缓冲区溢出。 (例如
strcpy(&ip_server[0], argv[1]);
) strncat
的第三个参数是第二个参数的最大长度,而不是连接字符串的最大长度。示例代码中提供的值允许缓冲区溢出。- IP 号前没有插入所需的 space 字符。
- 过度依赖固定大小的数组。
更重要的是,通用方法既难读又低效。 C 中的字符串处理有点烦人,但自 1989 年以来,C 字符串库有了重大改进,使用新的库功能可以使您的代码更安全、更易读,甚至更高效。好的 C 代码应该充分利用可用的库功能。
(在原始代码中重复使用 strncat
是低效的,因为 strncat
会在每次调用时从头开始重新扫描输出字符串,随着字符串变长,导致执行时间成二次方.)
更好的方法是使用 snprintf
提供简单、易读、安全和高效的格式化操作:
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Too few arguments.\n");
exit(1);
}
/* These are for documentation; no copying is involved */
const char* file_name = argv[2];
const char* ip_server = argv[1];
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
int outlen = snprintf(tftp_cmd, sizeof tftp_cmd,
"tftp -g -r %s %s", file_name, ip_server);
if (outlen >= sizeof tftp_cmd) {
fprintf(stderr, "Arguments are too long\n");
exit(1);
}
printf("%s\n", tftp_cmd);
return 0;
}
这可以通过分配内存而不是使用固定大小的缓冲区来改进:
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Too few arguments.\n");
exit(1);
}
char* tftp_cmd = NULL;
const char* file_name = argv[2];
const char* ip_server = argv[1];
int outlen = snprintf(tftp_cmd, 0,
"tftp -g -r %s %s", file_name, ip_server);
tftp_cmd = malloc(outlen + 1);
snprintf(tftp_cmd, outlen + 1,
"tftp -g -r %s %s", file_name, ip_server);
printf("%s\n", tftp_cmd);
free(tftp_cmd);
return 0;
}
一些现代 C 库实现了 asprintf
,它会自动分配内存,而且更加方便,因为它避免了调用 snprintf
两次。
int main(int argc, char** argv) {
if (argc < 3) {
fprintf(stderr, "Too few arguments.\n");
exit(1);
}
char* tftp_cmd = NULL;
const char* file_name = argv[2];
const char* ip_server = argv[1];
if (0 > asprintf(&tftp_cmd,
"tftp -g -r %s %s", file_name, ip_server)) {
fprintf(stderr, "Memory allocation error\n");
exit(1);
}
printf("%s\n", tftp_cmd);
free(tftp_cmd);
return 0;
}
以下代码更正了发布的代码中发现的问题。
int main(int argc, char *argv[])
{
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
char ip_server[IP_MAX_LEN];
char file_name[FILE_MAX_LEN];
const char * tftp_get = "tftp -g -r ";
if( 3 != argc )
{
fprintf( stderr, "USAGE: %s <serverIP> <filename>\n", argv[0]);
exit( EXIT_FAILURE );
}
// implied else, correct number of command line parameters
strcpy(ip_server, argv[1]);
strcpy(file_name, argv[2]);
// tftp_cmd[0] = '[=10=]'; -- not needed because first data is set by strcpy()
strcpy(tftp_cmd, tftp_get);
printf("tftp get command = %s\n", tftp_cmd);
strcat(tftp_cmd, file_name);
printf("tftp get command = %s\n", tftp_cmd);
strcat(tftp_cmd, ip_server );
printf("tftp get command = %s\n", tftp_cmd);
return 0
}
然而,对 strcat()
的一系列调用可能(可能)溢出 tftp_cmd[]
缓冲区。并且发布的代码正在尝试使用 strncat()
因此以下代码更安全,因为它不会溢出 tftp_cmd[] 缓冲区。
int main(int argc, char *argv[])
{
char tftp_cmd[TFTP_MAX_BUFFER_SIZE];
char ip_server[IP_MAX_LEN];
char file_name[FILE_MAX_LEN];
const char * tftp_get = "tftp -g -r ";
if( 3 != argc )
{
fprintf( stderr, "USAGE: %s <serverIP> <filename>\n", argv[0]);
exit( EXIT_FAILURE );
}
// implied else, correct number of command line parameters
strcpy(ip_server, argv[1]);
strcpy(file_name, argv[2]);
// tftp_cmd[0] = '[=11=]'; -- not needed because first data is set by strcpy()
strncpy(tftp_cmd, tftp_get, TFTP_MAX_BUFFER_SIZE);
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, file_name. TFTP_MAX_BUFFER_SIZE - strlen( tftp_cmd ) );
printf("tftp get command = %s\n", tftp_cmd);
strncat(tftp_cmd, ip_server, TFTP_MAX_BUFFER_SIZE - strlen( tftp_cmd );
printf("tftp get command = %s\n", tftp_cmd);
return 0
}
然而,这并没有提供任何迹象表明缓冲区实际上包含整个数据字符串。
所以可以在 return
语句
if( (strlen( tftp_get) + strlen( file_name ) + strlen( ip_server ) +1 ) > TFTP_MAX_BUFFER_SIZE )
{
printf( "unable to create the full contents of the tftp command\n" );
}