【问题标题】:String Concatenation using a variadic function in C使用 C 中的可变参数函数进行字符串连接
【发布时间】:2014-10-06 09:35:11
【问题描述】:

我正在尝试用 C 编写一个基本的测验程序。它基本上会存储卡片和答案。 但与此同时,我正在尝试使用我学到的新技术,如可变参数函数和动态内存分配。

我希望程序能够在我更改常量时进行扩展,不应该有 K&R 定义的“幻数”。问题是我不能在 fscanf 的格式参数中使用变量。我需要手动定义字符串长度。为了克服这个限制,我尝试编写一个字符串连接函数,它将生成 fscanf 格式参数。

例如

char * scanf_string = et_concat(5, "%", CARD_SIZE, "s | %", ANSWER_SIZE, "s");

常量在 consts.h 中定义

#define CARD_SIZE 200
#define ANSWER_SIZE 1000
#define CONCAT_SIZE 20

et_concat 函数在 etstring.h 中。 这是发生分段错误的地方。

#include <stdarg.h>
#include <string.h>

char * et_concat (int count, char * str, ...)
{
    va_list ap;
    int j;
    char *concatted_string = (char *) malloc (count*CONCAT_SIZE+1);

    va_start(ap, str);
    for (j = 0; j < count; j++) {
        strcat(concatted_string, va_arg(ap, char *));
    }
    va_end(ap);

    return concatted_string;
}

我试图调用 et_concat 的代码在 reader.c 中

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "consts.h"
#include "etstring.h"

int iterate_inputs()
{
  char card[CARD_SIZE];
  char answer[ANSWER_SIZE];
  FILE *fp = fopen("data.txt","r");
  if (fp == NULL)
  {
    return EXIT_FAILURE;
  }
  char * scanf_string = et_concat(5, "%", CARD_SIZE, "s | %", ANSWER_SIZE, "s");
  printf(scanf_string);
  fscanf(fp, scanf_string, card, answer);
  printf("%s | %s\n", card, answer);
  fclose(fp);
  return EXIT_SUCCESS;
}

非常感谢。

【问题讨论】:

  • 您的函数需要 5 个 C 字符串,但 CARD_SIZEANSWER_SIZE 是整数常量。您可以编写一个函数或宏来对它们进行字符串化。
  • printf(str) - 请不要那样做。如果字符串恰好包含%,会发生什么?请使用printf("%s", str)puts(str)

标签: c malloc dynamic-memory-allocation scanf variadic-functions


【解决方案1】:

您的连接例程有各种错误和缺点:

  • 您已经读取了非可变参数str 中的第一个字符串,但您只打印可变参数,其中只剩下四个。你基本上跳过了第一个字符串,第五个字符串是垃圾。

  • 分配字符串后,不要初始化它,以免它包含垃圾。将第一个字符设置为'\0' 足以初始化一个以零结尾的空字符串。

  • 您分配了一个固定的缓冲区,并没有真正检查溢出。这有点违反直觉:要么分配一个缓冲区(稍后必须释放)并允许任意长度的字符串,要么传入一个长度受限的缓冲区,而不关心例程中的分配。

    李>

还有一个问题是你的论点不都是字符串。一个简单的解决方案是使用strinfification macro。您必须首先扩展参数,以便您的字符串具有实际值,而不是宏的名称,这对scanf 没有任何意义。这个解决方案的问题是格式字符串中给出的大小不包括终止'\0',因此您应该像这样分配缓冲区:

char card[CARD_SIZE + 1];

这是您的实现的更正版本,但它仍然不检查缓冲区溢出:

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#define CARD_SIZE 200
#define ANSWER_SIZE 1000
#define CONCAT_SIZE 20

char *et_concat(int count, ...)
{
    va_list ap;
    char *concatted_string = malloc(count * CONCAT_SIZE + 1);
    int j;

    *concatted_string = '\0';

    va_start(ap, count);
    for (j = 0; j < count; j++) {
        strcat(concatted_string, va_arg(ap, char *));
    }
    va_end(ap);

    return concatted_string;
}

#define STR1(X) #X
#define STR(X) STR1(X)

int main()
{
    char *scanf_string = et_concat(5, 
        "%", STR(CARD_SIZE), "s | %", STR(ANSWER_SIZE), "s");

    printf("'%s'\n", scanf_string);
    free(scanf_string);

    return 0;
}

编辑我在代码中写了STR1(CARD_SIZE),而它应该只是STR。现已修复。

【讨论】:

  • +1 用于实际回答问题(而不是像我那样回避)。 :) 感谢您对我的帮助。
  • 非常感谢,我不知道 C 预处理器能做什么。这解决了我的问题。对于任何对字符串化感兴趣的人,[链接] (gcc.gnu.org/onlinedocs/cpp/Stringification.html) 非常有用。再次感谢您的详尽回答。
  • 对于任何尝试使用代码的人:STR1(X) 返回常量名称 STR(X) 返回常量值。在 scanf_string 声明中应该是 STR(CARD_SIZE)。
  • @JackJones:是的,两次都应该是STR。 (为了不写任何废话,我修改了代码,并在粘贴时将其留在了其中。抱歉疏忽。我将编辑答案。)
【解决方案2】:

有一种更简单的方法来构造您的格式字符串。由于“变量”是编译时常量,您可以使用宏stringify它们,然后让编译器为您连接它们。编译器会自动将相邻的字符串常量连接成一个字符串,例如。 "this" "that" -> "thisthat"坦白:我完全检查了linked question 以使这些(简单!?)宏正常工作。

$cat strfy.c
#define CARD_SIZE 200
#define ANSWER_SIZE 20
#define XSTR(x) STR(x)
#define STR(x) #x

printf("%" XSTR(CARD_SIZE) "s | %" XSTR(ANSWER_SIZE) "s", card, answer);

$cpp -P strfy.c
printf("%" "200" "s | %" "20" "s", card, answer);

正如 M Oehm 指出的那样,您仍然需要这种类型的宏和可变参数函数才能将整数折叠为字符串。否则,函数需要更复杂才能处理不同类型的参数。

还有一种方法是使用sprintf 来构建格式字符串。但是这种方式有额外的复杂性,您需要预先计算字符串的大小。这个问题可以通过使用snprintfNULL作为目标字符串来解决,返回值就是需要的大小。

#define CARD_SIZE 200
#define ANSWER_SIZE 1000
#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *fmt;
    size_t fmt_sz;
    char card[CARD_SIZE + 1];
    char answer[ANSWER_SIZE + 1];

    fmt = malloc(fmt_sz = snprintf(NULL, 0, "%%%ds | %%%ds", CARD_SIZE, ANSWER_SIZE) + 1);
    snprintf(fmt, fmt_sz, "%%%ds | %%%ds", CARD_SIZE, ANSWER_SIZE);
    //printf("%s", fmt);
    scanf(fmt, card, answer);

    return 0;
}

【讨论】:

  • 好答案。我喜欢snprintf 的建议。毕竟,如果你的格式都是十进制数字,那么估计最大字符串长度是很容易的,不会造成浪费。
  • 也非常感谢。对我来说,字符串化更容易理解和实现。我选择了 M Oehm 的答案,因为他还修复了我的可变参数函数。
猜你喜欢
  • 2017-05-15
  • 2014-03-15
  • 2015-08-08
  • 2014-12-18
  • 2017-12-26
  • 2022-01-08
  • 1970-01-01
  • 2018-05-28
  • 2018-11-18
相关资源
最近更新 更多