在 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" );
    }