簡體   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