#Buffered stdout echo function

8 messages · Page 1 of 1 (latest)

lavish inlet
#

[code below, first context]

The context is to have a method applib_echo(const char *restrict, ...); that would allow me to print as much as possible during program life time.

The expected API would go like this:

// In main function:

int main(void)
{
  applib_echo_init();

  // whatever code here

  applib_echo_dispose();
}

and in a code, it would go like this:

// some computations
applib_echo("Hello %s\n", "World");
// some other computations

Direct usage of printf wouldnt work, because I want that no prints happen if the second computation fails (for example, abort or exit)

one way, suggested by Nekro, is the open stdout in fully-buffered mode. I did not implement that one solution because I would need to make sure the BUFSIZ capacity is not reached anyway.

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


#define DIE ((void)(fflush(stdout),printf("---------------\nFAILURE file %s, line %d\n", __FILE__, __LINE__),fflush(stdout),abort()))


#ifdef APP_LIB_I
struct App_EchoBuffer
{
  size_t size;
  size_t capacity;
  char *memory;
} applib_echo_buffer;


static void applib_echo_invariants(void)
{
  assert(applib_echo_buffer.capacity != 0u);
  assert(applib_echo_buffer.size <= applib_echo_buffer.capacity);
  assert(applib_echo_buffer.capacity - applib_echo_buffer.size >= 1u);
  assert(!applib_echo_buffer.memory || applib_echo_buffer.memory[applib_echo_buffer.size] == 0);
}


static void applib_echo_ensure(size_t incoming_string_size)
{
  applib_echo_invariants();
  
  size_t next_capacity = applib_echo_buffer.capacity;
  while(next_capacity - applib_echo_buffer.size - 1u <= incoming_string_size) {
    next_capacity *= 2u;
    if(next_capacity <= applib_echo_buffer.capacity) {
      DIE;
    }
  }
  
  if(next_capacity > applib_echo_buffer.capacity || !applib_echo_buffer.memory) {
    void *next_memory = realloc(applib_echo_buffer.memory, next_capacity * sizeof(*applib_echo_buffer.memory));
    if(!next_memory) {
      DIE;
    } else {
      applib_echo_buffer.memory = next_memory;
      applib_echo_buffer.capacity = next_capacity;
    }
  }
  
  applib_echo_invariants();
}


static void applib_echo_accept(char const *tpl, va_list args, size_t incoming_string_size)
{
  applib_echo_invariants();
  applib_echo_ensure(incoming_string_size);
  assert(applib_echo_buffer.memory);
  const int r = vsnprintf(
    applib_echo_buffer.memory + applib_echo_buffer.size,
    applib_echo_buffer.capacity - applib_echo_buffer.size,
    tpl, args
  );
  if(r < 0 || (size_t)r != incoming_string_size) {
    DIE;
  }
  applib_echo_buffer.size += incoming_string_size;
  applib_echo_invariants();
}
#

and the three API functions:


void applib_echo_init(void)
{
  applib_echo_buffer.size = 0u;
  applib_echo_buffer.capacity = 4u; // Consider this as an example
  applib_echo_buffer.memory = (void*)0;
  applib_echo_invariants();
}


void applib_echo_dispose(void)
{
  applib_echo_invariants();
  fputs(applib_echo_buffer.memory, stdout);
  fflush(stdout);
  free(applib_echo_buffer.memory);
  applib_echo_buffer.memory = (void*)0;
}


void applib_echo(const char *restrict tpl, ...)
{
  va_list lst_;
  va_list lst;
  va_start(lst_, tpl);
  va_copy(lst, lst_);
  const int r = vsnprintf(0, 0, tpl, lst_);
  if(r > 0) {
    applib_echo_accept(tpl, lst, (size_t) r);
  }
  va_end(lst);
  va_end(lst_);
  if(r < 0) {
    DIE;
  }
}
fleet minnow
#

you will need some sort of explicit flush procedure for the echo buffer

lavish inlet
#

actually no, that is the point: the internal in-memory buffer should not be flushed before disposal!

#

it really needs to keep all the stuff internally as much as possible, and then eventually push it on stdout when the application ends.

(it might, in the far future, be required to use some kind of temporary file and automatically flush there, if emmory grows too much. however, it should be entirely hidden behind the implementation)

fleet minnow
#

right, just write to an internal buffer and only flush to stdout if and when dispose is called