Home > database >  C ansi format macro global char[] overwrite
C ansi format macro global char[] overwrite

Time:02-03

I have a coding assignment that must be done in C. I got sidetracked and decided to try to make a format macro i can use for styling text with ansi codes. My code is as follows:
<format.h>

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

/**
 * global buffer to store format arguments for prints
 */
char format_buffer[128];

/**
 * Count the number of char* in variadic arguments, auxiliary for FMT macro
 */
#define FARGS(...)  (sizeof((char*[]){__VA_ARGS__})/sizeof(char*))

/**
 * Automatically fills in parameters buffer and argc for the format function. Buffer is a global variable, therefore it is never out of scope.
 */
#define FMT(...) format(format_buffer, FARGS(__VA_ARGS__), __VA_ARGS__)

#define A_START "\x1B["
#define A_END "m"

/**
 * creates a format string based on input parameters.
 * @param argc the number of ansi flags to use
 * @param buffer the buffer to write the format string into. A size of 64 should be enough for most reasonable uses
 * @param ... the ansi flags to use. Examples include RED, BLUE, DASH, etc
 * @return the same pointer buffer, for ease of use in printf functions
 */
char* format(char* buffer, int argc, ...);

// ansi macros for formatting, all of the style ";NN"


<format.c>


#include "format.h"

char format_buffer[128] = {};

char* format(char* buffer, int argc, ...){
    buffer[0] = '\0';

    va_list argv;
    int i;

    // initialize argv to get data from the variable arguments
    va_start(argv, argc);

    strcat(buffer, A_START);
    /* access all the arguments assigned to valist */
    for (i = 0; i < argc; i  ) {
        strcat(buffer, va_arg(argv, char*));
    }
    strcat(buffer, A_END);

    /* clean memory reserved for valist */
    va_end(argv);

    return buffer;

}

Using that, I can call the macro as follows, which is what I want:
printf("%sHello!\n", FMT(RED, BOLD)) //prints <Hello!\n> in red and bold

The problem

The problem I have is when I try to use multiple calls in the same print statement:
printf("%sHello, %sWorld!\n", FMT(RED, BOLD), FMT(YELLOW)) //prints <Hello, World!\n> in yellow
I'm positive it's not working as expected because FMT(...) always returns the same global char*, but I don't know how could i change it so that:

  • I can call the format macro as shown above: printf("%sHello!\n", FMT(RED, BOLD)).
  • I can use multiple FMT calls in the same print statement, as in printf("%sHello, %sWorld!\n", FMT(RED, BOLD), FMT(YELLOW)) should print <Hello, > in red and bold and <World!\n> in yellow.

As a final note, I'd rather code the solution instead of using a library or header that already has an implementation of this.

I first tried creating a new char[] inside the format function, but it would be stored in the stack so I assume that's a much worse outcome.

CodePudding user response:

Simply change the macro to:

#define FMT(...) format((char[128]){}, FARGS(__VA_ARGS__), __VA_ARGS__)

https://godbolt.org/z/KvWWcYfPb

CodePudding user response:

After reading both comments from Jonathan Leffler and the answer from 0___________ I've changed the files to this:
<format.h>

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

/**
 * Count the number of char* in variadic arguments, auxiliary for FMT macro
 */
#define FARGS(...)  (sizeof((char*[]){__VA_ARGS__})/sizeof(char*))

#define FMT_BUF_SIZE 64

/**
 * Automatically fills in parameters buffer and argc for the format function. 
 */
#define FMT(...) format((char[FMT_BUF_SIZE]){""}, FARGS(__VA_ARGS__), __VA_ARGS__)

#define FMT_START "\x1B["
#define FMT_END "m"

// ansi flags for text styling, there's a lot of them

//black
#define BLACK ";30"
//red
#define RED ";31"
//green
#define GREEN ";32"
//yellow
#define YELLOW ";33"
//blue
#define BLUE ";34"
//magenta
#define MAGENTA ";35"
//cyan
#define CYAN ";36"
//bright gray
#define GRAY_B ";37"

//black background
#define BLACK_BG ";40"
//red background
#define RED_BG ";41"
//green background
#define GREEN_BG ";42"
//yellow background
#define YELLOW_BG ";43"
//blue background
#define BLUE_BG ";44"
//magenta background
#define MAGENTA_BG ";45"
//cyan background
#define CYAN_BG ";46"
//bright gray background
#define GRAY_BG_B ";47"

//gray
#define GRAY ";90"
//bright red
#define RED_B ";91"
//bright green
#define GREEN_B ";92"
//bright yellow
#define YELLOW_B ";93"
//bright blue
#define BLUE_B ";94"
//bright magenta
#define MAGENTA_B ";95"
//bright cyan
#define CYAN_B ";96"
//white
#define WHITE ";97"

//gray background
#define GRAY_BG ";100"
//bright red background
#define RED_BG_B ";101"
//bright green background
#define GREEN_BG_B ";102"
//bright yellow background
#define YELLOW_BG_B ";103"
//bright blue background
#define BLUE_BG_B ";104"
//bright magenta background
#define MAGENTA_BG_B ";105"
//bright cyan background
#define CYAN_BG_B ";106"
//white background
#define WHITE_BG ";107"

#define DASH ";9"

#define BOLD ";1"
#define ITALICS ";3"
#define STRIKETHROUGH ";9"
// I can't explain this one
#define REVERSE ";7"
#define CLEAR ";0"

// underline light
#define UNDERLINE_L ";4"
// underline heavy
#define UNDERLINE_H ";21"

/**
 * creates a format string based on input parameters.
 * @param argc the number of ansi flags to use
 * @param buffer the buffer to write the format string into. A size of 64 should be enough for most reasonable uses
 * @param ... the ansi flags to use. Examples include RED, BLUE, DASH, etc
 * @return the same pointer buffer, for ease of use in printf functions
 */
char* format(char* buffer, int argc, ...);


<format.c>

#include "format.h"

char* format(char* buffer, int argc, ...){
    buffer[0] = '\0';

    va_list argv;
    int i;

    // calculate available memory in buffer available for ANSI codes
    int buffer_len = FMT_BUF_SIZE - strlen(FMT_START) - strlen(FMT_END) - 1;

    // initialize argv to get data from the variable arguments
    va_start(argv, argc);

    strcat(buffer, FMT_START);
    /* access all the arguments assigned to valist */
    for (i = 0; i < argc; i  ) {

        // retrieve ansi code and substract length to available length to prevent overflows
        char* code = va_arg(argv, char*);
        buffer_len -= strlen(code);

        // concatenate ansi code if there's space available in the buffer, else break out of loop
        if(buffer_len >= 0) strcat(buffer, code);
        else break;
    }
    strcat(buffer, FMT_END);

    /* clean memory reserved for valist */
    va_end(argv);

    return buffer;

}


The macro works as intended, and the format function will now stop reading arguments early if the format buffer capacity is exceeded (the buffer must at least be of length 4, and i believe the longest individual ansi code is 4 characters long).

  • Related