Converting fixed-width text file to CSV in C I'm trying to get reacquainted with C after years of higher-level languages.

I wrote a small fixed-width to CSV converter. It works (for ASCII at least) and it's quite fast, but I wonder if I missed anything or if it can be made even faster.

I'm reading the file line by line and process the desired columns (some can be skipped), placing them into an output 8K buffer. I'm using one function to do the substring, appending and trimming trailing space (just the space, in this case I don't want to bother testing for other whitespace characters).

#include &lt;ctype.h&gt;

// number of columns to process
#define COLS 3
#define LINE_SIZE 256
#define BUFFER_SIZE 8192

#define INFILE "in.txt"
#define OUTFILE "out.csv"

size_t RANGES[COLS][2] = {{0, 6}, {6, 20}, {29, 3}};

/*
 * Copy from source to destination, up to len chars, trimming trailing spaces
 * Returns number of chars actually copied
 */
int trimcpy(char *destination, char *source, size_t len) {
    // trim spaces from the end - we only care about the space char
    while (len&gt;0 &amp;&amp; source[len-1]==' ')
        len--;

    int i = 0;
    while (i&lt;len &amp;&amp; *source != '\0') {
        *destination++ = *source++;
        i++;
    }
    *destination = '\0';

    return i;
}

int main(void) {
    FILE *rfp;
    FILE *wfp;

    char line[LINE_SIZE];
    char out[BUFFER_SIZE];

    rfp = fopen(INFILE, "r");
    if (rfp == NULL)
        exit(EXIT_FAILURE);

    wfp = fopen(OUTFILE, "w");
    if (wfp == NULL)
        exit(EXIT_FAILURE);

    int p = 0;

    // fgets is 4x faster than getline!
    while (fgets(line, LINE_SIZE, rfp) != NULL) {
        // write buffer if almost full (largest column is 20 chars)
        if (p &gt; BUFFER_SIZE - 20) {
            fputs(out, wfp);
            p = 0;
        }

        // go through the columns
        for (int i=0; i&lt;COLS; i++) {
            p += trimcpy(out+p, line+RANGES[i][0], RANGES[i][1]);
            p += trimcpy(out+p, i&lt;COLS-1 ? "," : "\n", 1);
        }
    }

    // write any remaining data in buffer
    fputs(out, wfp);

    fclose(rfp);
    fclose(wfp);

    exit(EXIT_SUCCESS);
}
Consider const char *

const char *source allows the function to be used with const strings, better conveys code's functionality and more readily allows for optimizations.

// int trimcpy(char *destination, char *source, size_t len) {
int trimcpy(char *destination, const char *source, size_t len) {


Undefined behavior

trimcpy() begins by examining later elements of source[] even though they are not known to have been assigned.  The string may not be as long as len.

int trimcpy_alt(char *destination, char *source, size_t len) {
   // Suggest memchr() here rather than strlen() to not look too far.
   char *null_character_pos = memchr(source, '\0', len);
   if (null_character_pos) len = null_character_pos - source;
   ...


Undefined behavior 2

In a selective case of an empty file, the first call to fgets() returns NULL and then the following fputs(out, wfp); is UB as out contents are not initialized.  Add initialization or assignment.

char out[BUFFER_SIZE];
out[0] = '\0'; // add


Not trimming end spaces before \n

trimcpy() does not trim spaces just before '\n'.  I suspect this in not in line with OP's goals.

Avoid redundant information

Redundant information takes more work to maintain.   Consider dropping the 3

// #define COLS 3
size_t RANGES[][2] = {{0, 6}, {6, 20}, {29, 3}};
#define COLS (sizeof RANGES/sizeof RANGES[0])




Minor things:

Mixing types

Little reason to mix size_t and int types here for array indexing and sizing.  Recommend to  use just one: size_t (my preference) or 2) int, but not  both.

// From
int trimcpy(char *destination, char *source, size_t len) {
  ...
  int i = 0;
  while (i&lt;len &amp;&amp; *source != '\0') {

// To
size_t trimcpy(char *destination, char *source, size_t len) {
  ...
  size_t i = 0;
  while (i&lt;len &amp;&amp; *source != '\0') {


main()  

  // int p = 0;
  size_t p = 0;




Subtraction with unsigned types

If above size_t employed, consider the 2 below:  Which works well should a later version of code surprisingly  define  #define BUFFER_SIZE 19?

if (p &gt; BUFFER_SIZE - 20) {
if (p + 20 &gt; BUFFER_SIZE) {



   The first is the same as if (p &gt; SIZE_MAX) { or if (0) {