在C语言中使用可变函数进行字符串连接

在C语言中使用可变函数进行字符串连接,c,malloc,dynamic-memory-allocation,scanf,variadic-functions,C,Malloc,Dynamic Memory Allocation,Scanf,Variadic Functions,我正在尝试用C语言编写一个基本的测验程序。它基本上会存储卡片和答案。 但与此同时,我正在尝试使用我学到的新技术,如可变函数和动态内存分配 我希望程序能够随着我改变常数而扩展,不应该有K&R定义的“幻数”。问题是我不能在fscanf的format参数中使用变量。我需要手动定义字符串长度。为了克服这个限制,我尝试编写一个字符串concatation函数,该函数将生成fscanf format参数 e、 g 常数在常量h中定义 #define CARD_SIZE 200 #define ANSWER_

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

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

e、 g

常数在常量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(整数计数,字符*str,…)
{
va_列表ap;
int j;
char*concatted_string=(char*)malloc(count*concated_SIZE+1);
va_启动(ap、str);
对于(j=0;j
我试图从reader.c中调用et_concat的代码

#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;
}
#包括
#包括
#包括
#包括“常数h”
#包括“etstring.h”
int迭代_输入()
{
字符卡[卡片大小];
字符应答[应答大小];
文件*fp=fopen(“data.txt”,“r”);
如果(fp==NULL)
{
返回退出失败;
}
char*scanf_string=et_concat(5,“%”,卡片大小,“s,”,答案大小,“s”);
printf(扫描字符串);
fscanf(fp、扫描字符串、卡片、应答);
printf(“%s |%s\n”,卡片,答案);
fclose(fp);
返回退出成功;
}

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

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

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

#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;
}
#定义卡尺寸200
#定义答案大小1000
#包括
#包括
int main()
{
char*fmt;
尺寸(单位)(平方米);;
字符卡[card_SIZE+1];
字符应答[应答大小+1];
fmt=malloc(fmt_sz=snprintf(NULL,0,%%%ds |%%ds),卡片大小,答案大小)+1);
snprintf(fmt、fmt_sz、%%ds |%%ds)、卡片大小、答案大小);
//printf(“%s”,fmt);
扫描(fmt、卡片、应答);
返回0;
}

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

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

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

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

还有一个问题是,您的参数并不都是字符串。解决这个问题的一个简单方法是使用。必须首先展开参数,使字符串具有真实值,而不是宏名称,这对
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;
}
#包括
#包括
#包括
#包括
#定义卡的大小为200
#定义答案大小1000
#定义CONCAT_尺寸20
字符*et_concat(整数计数,…)
{
va_列表ap;
char*concatted_string=malloc(count*concated_SIZE+1);
int j;
*concatted_字符串='\0';
va_启动(ap,计数);
对于(j=0;j

Edit我在代码中写了
STR1(卡片大小)
,而它应该是
STR
。现已修复。

您的函数需要5个C字符串,但
CARD\u SIZE
ANSWER\u SIZE
是整数常量。你可以编写一个函数或宏来字符串化它们。
printf(str)
-请不要这样做。如果字符串恰好包含
%
,会发生什么情况?请使用
printf(“%s”,str)
put(str)
。回答得好。我喜欢
snprintf
的建议。毕竟,如果您的格式都是十进制数,那么很容易估计最大字符串长度而不会造成浪费。非常感谢。对我来说,严格化要简单得多
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;
}