【问题标题】:concatenate const strings and variable inputs in C在 C 中连接 const 字符串和变量输入
【发布时间】:2015-12-17 14:14:45
【问题描述】:

我正在使用 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] = '\0';
    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
}

此应用返回:

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

我正在使用好方法吗?

【问题讨论】:

  • 只需使用sprintf...
  • &ip_server[0] 这样的东西在这里混淆了你的意图。当您要使用数组时,最好使用数组的名称:ip_server。虽然在技术上是相同的,但它不仅使您的意图更清楚地使用第一个条目(并且更少输入和阅读)..
  • 对于这种行:strcpy(&ip_server[0], argv[1]);,在没有首先检查argc 以确保命令行参数是由用户实际输入的情况下,永远不要访问 argv[] 中除argv[0] 之外的任何条目。当参数数量不正确时,输出'usage'语句以显示使用正确命令行的格式/语法

标签: c string parameters concatenation


【解决方案1】:

显然,所提供的代码存在各种问题:

  • 参数被不必要地复制到临时缓冲区中,没有检查缓冲区溢出。 (例如strcpy(&ip_server[0], argv[1]);
  • strncat 的第三个参数是第二个参数的最大大小,而不是串联字符串的最大大小。示例代码中提供的值允许缓冲区溢出。
  • 在 ip 号之前未插入所需的空格字符。
  • 过度依赖固定大小的数组。

更重要的是,通用方法既难以阅读,也效率低下。 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;

}

【讨论】:

  • 感谢这些例子。最后一个似乎很有效!只是一个问题(可能很愚蠢),但在您的第二个示例代码中,为什么作为参数给 snprintf() 的第二个参数等于outlen + 1。审讯的重点是1
  • @SnP:阅读snprintf 的文档。您作为第二个参数提供的长度是缓冲区的大小,它必须为终止 NUL 留出空间。它返回的长度是输出中的字符数,不包括终止的 NUL。
【解决方案2】:

首先,请注意&amp;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() 可能非常危险,因为它可能会忽略终止的'\0'

tftp_cmdstrcpy() 的第一个参数时,您也不需要初始化它,但您需要它指向足够大的内存缓冲区。而且你根本不需要复制argv[1]argv[2],直接使用就可以了。如果必须,您可以使用指针而不是复制昂贵的内存。

最后,正如@EugeneSh.在这个comment中所建议的,最好的方法是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;
}

【讨论】:

  • 感谢所有这些可靠的提示。我通过这篇文章学到了很多东西,并且解决方案完全符合我的预期!只是一个问题,为什么输入缓冲区被指定为 NULL 作为第一个 snprintf() 参数?
【解决方案3】:

以下代码更正了发布代码中发现的问题。

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] = '\0'; -- 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] = '\0'; -- 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" );
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-17
    • 1970-01-01
    • 2016-11-15
    • 2018-06-23
    相关资源
    最近更新 更多