繁体   English   中英

在C中使用可变参数函数进行字符串串联

[英]String Concatenation using a variadic function in C

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

我希望程序能够随着我更改常数而缩放,因此应该没有K&R定义的“幻数”。 问题是我不能在fscanf的format参数中使用变量。 我需要手动定义字符串长度。 为了克服此限制,我尝试编写一个字符串合并函数,该函数将生成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;
}

首先十分感谢。

您的串联例程存在各种错误和缺点:

  • 您已经阅读了非可变参数str的第一个字符串,但是仅打印了可变参数,仅剩四个。 您实际上跳过了第一个字符串,而第五个字符串是垃圾。

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

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

还有一个问题是您的参数不是全部字符串。 一个简单的解决方案是使用strinfification宏 您必须首先扩展参数,以便您的字符串具有实际值,而不是宏的名称,这对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 立即修复。

有一种更简单的方式来构造格式字符串。 由于“变量”是编译时常量,因此可以使用宏对它们进行字符串化处理 ,然后让编译器为您串联它们。 编译器将自动将相邻的字符串常量连接为单个字符串,例如。 "this" "that" -> "thisthat" 告白:我完全签出了链接的问题,以使这些(简单!?)宏正常工作。

$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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM