0

I have a single function void* producer(void* param) that is passed to multiple POSIX threads and whose goal is to read each line of a file OR each line of a string (char*) array and process them.

Question : How can make this function to adapt elegantly to these two possible inputs? (I guess the first one would be the filename of the file or a FILE*).

So far, the function can only read a file and here is its code :

void* producer(void* param)
{
    FILE* filep = *(FILE*)param;

    char line[256];

    while (fgets(line, 256, filep) != NULL) 
    {       
        processLine(line);
    }

    return NULL;
}
3
  • 2
    Creepy :) Best make param point to a structure or union, in which you explicitly tell it what to use. Commented May 6, 2016 at 11:12
  • 1
    Using a memory mapped file, with mmap(), you simply pass a char * for both eventualities. Even the code is the same. Commented May 6, 2016 at 11:16
  • 1
    @Frankie_C This is untrue - the string will be zero terminated, while a memory-mapped file won't be. (Accessing the byte past the end will crash if the file size is aligned with page size.) To cover both options, the OP would need to send the string size along with the string, which brings back the original question how to pack two things in a single void *. Also, memory mapping is unportable in C and can fail if the file is not a regular file (e.g. if it's a pipe). Commented May 6, 2016 at 11:47

1 Answer 1

2

Provide a structure that knows how to produce the next line depending on its contents. For example (untested):

typedef struct {
  FILE *fp;
  const char *str;
} Source;

Source *source_new_from_file(const char *filename)
{
  Source *ret = malloc(sizeof(Source));
  if (!ret)
    return NULL;
  ret->fp = fopen(filename, "rb");
  if (!ret->fp)
    return NULL;
  ret->str = NULL;
  return ret;
}

Source *source_new_from_str(const char *str)
{
  Source *ret = malloc(sizeof(Source));
  if (!ret)
    return NULL;
  ret->fp = NULL;
  ret->str = str;
  return ret;
}

bool source_read_line(Source *s, char *dest, size_t destsize)
{
  if (s->fp)
    return fgets(dest, destsize, s->fp) != NULL;

  const char *newline = strchr(s->str, '\n');
  if (!newline)
    // handle trailing line without newline, like fgets does
    newline = s->str + strlen(s->str);
  if (newline == s->str)
    return false;  // no more data

  size_t linelen = newline - s->str;
  if (linelen > destsize - 1)
    linelen = destsize - 1;
  memcpy(dest, s->str, linelen);
  dest[linelen + 1] = '\0';
  s->str = newline;
  return true;
}

// ...

void* producer(void* param)
{
  Source *source = param;
  char line[256];

  while (source_read_line(source, line, sizeof line)) {       
    processLine(line);
  }

  return NULL;
}

With this code you can pass any kind of source to the producer. Even if a new kind of source appears (say, the resultset of a database query, or an XML document where "lines" are encoded as XML elements), you will be able to implement the new functionality only by extending the source_read_line, and without changing any of the code in producer.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.