0

I have a variable char *cmd and I want to store a string in this from fgets(). Is there a way to dynamically allocate memory using malloc or something similar for this variable? Or do I have to predefine its size and null terminate after? Before, I had the size of cmd predefined to 100 but I was trying to find where I was getting a segmentation fault.

char *cmd; 

fgets(cmd, n, stdin);

I am then trying to tokenize the cmd string using strtok() with whitespace as the delimiter.

Brad Bieselin
  • 107
  • 1
  • 11
  • 1
    There isn't a language feature built in, but there's nothing from preventing you from dynamically allocating a buffer and growing that buffer as needed if `fgets()` doesn't get a full line in one go. You can tell by whether the last character in the buffer used by `fgets()` is a newline. – torstenvl Dec 09 '18 at 01:35
  • You pass a fixed size string to `fgets()`. Look up POSIX `getline()` for a line reading function that allocates space as needed. – Jonathan Leffler Dec 09 '18 at 01:36
  • I tried using char cmd[200]; and then fgets(cmd, 200, stdin); but when using strcopy() to store cmd into a char pointer *token, I get a segmentation fault. – Brad Bieselin Dec 09 '18 at 01:55
  • 1
    Pointers don't store strings; they point at strings. – melpomene Dec 09 '18 at 02:10
  • `char *cmd;` creates the *Uninitialized* pointer `cmd` that holds an *Indeterminate* address as its value. Before you can copy anything to `cmd` you must initialize it by assigning the starting address to a valid block of memory to `cmd`. That is generally done either by allocating new storage, e.g. `cmd = malloc (len + 1);` to hold a string of `len` character; or by assigning an existing block, e.g. `char buf[1024], *cmd = buf;`. In the first case, you can `realloc`, not so with the second. – David C. Rankin Dec 09 '18 at 03:44
  • 1
    **"but I was trying to find where I was getting a segmentation fault."** - post your attempt and we are happy to help, but right now, the S.O. crystal-ball is on the fritz... You have multiple issues in your question (1) is there a way I can allocate or do I need a fixed buf with `fgets` - you can do either, but you must allocate or declare the fixed buf before the call to `fgets`; (2) the SegFault; and (3) tokenizing with `strtok` on whitespace (don't forget line-endings are whitespace). Please provide [A Minimal, Complete, and Verifiable Example (MCVE)](http://stackoverflow.com/help/mcve). – David C. Rankin Dec 09 '18 at 04:22

1 Answers1

1

As I said in a comment above, this isn't a built-in feature of standard C, although there is a POSIX function that does what you're looking for. If you want to roll your own, the way to do it is to dynamically allocate (and reallocate) memory to store the results you get from fgets(). You can tell when fgets() reaches the end of the line because it'll either abruptly reach the end of the file (in which case it will return NULL) or because the last character it returns will be the newline.

Here's an example function. Note that I use strrchr() to search backwards from the end of the buffer for a newline character. If we get a hit, we make sure to toss out the newline in our returned string, and make sure to break out of the while() loop so that we don't call fgets() again and start getting the next line.

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

// NOTE:  dygetline() allocates memory!  Do not disregard the return and
// remember to free() it when you're done! 

#define BSZ 1024

char *dygetline(FILE * restrict stream) {
  char *ret = NULL;
  char *temp = NULL;
  size_t retalloc = 1;
  char buffer[BSZ];
  size_t buflen = 0;
  char *nlsrch;


  while (NULL != fgets(buffer, BSZ, stream)) {
    nlsrch = strrchr(buffer, '\n');

    if (nlsrch) *nlsrch = '\0';     // Remove newline if exists

    // Get the size of our read buffer and grow our return buffer
    buflen = strlen(buffer);
    temp = realloc(ret, retalloc + buflen);

    if (NULL == temp) {
      fprintf(stderr, "Memory allocation error in dygetline()!\n");
      free(ret);
      return NULL;
    }

    ret = temp;                     // Update return buffer pointer

    strcpy((ret+retalloc-1), buffer);  // Add read buffer to return buffer
    retalloc = retalloc + buflen;      // Housekeeping

    if (nlsrch) break;              // If we got a newline, stop
  }

  // If there was a file read error and fgets() never got anything, then
  // ret will still be NULL, as it was initialized.  If the file ended
  // without a trailing newline, then ret will contain all characters it
  // was able to get from the last line.  Otherwise, it should be well-
  // formed. 

  return ret;
}
torstenvl
  • 779
  • 7
  • 16
  • Note: `strrchr()` and `strlen()` makes 2 passes through the `n` characters of `buffer`. Only `strlen(buffer)` is [needed](https://stackoverflow.com/a/27729970/2410359) to find a potential `'\n'` and know the length. – chux - Reinstate Monica Dec 10 '18 at 02:48
  • 1
    `retalloc + buflen` is 1 too small. Suggest `realloc(ret, retalloc + buflen + 1);` – chux - Reinstate Monica Dec 10 '18 at 02:50
  • 1
    `strcat(ret, buffer);` is increasingly progressively slow with each successive concatenation. Simply drop it and replace `if (retalloc==0) ret[0]='\0';` with `strcpy(ret + retalloc, buffer);` or `memcpy(ret + retalloc, buffer, buflen + 1);` – chux - Reinstate Monica Dec 10 '18 at 02:54
  • We only need one null terminator, no matter how many times we go through `fgets()`, so starting `retalloc` off at `1` should do the trick, rather than growing it by an extra byte for every time we call `fgets()`. Good catch though. – torstenvl Dec 10 '18 at 03:02
  • 1
    `if (retalloc==1) ret[0]='\0';` is not needed. (And `realloc(ret, retalloc + buflen + 1)` would not "growing it by an extra byte for every time"). Still good upgrades. – chux - Reinstate Monica Dec 10 '18 at 03:19
  • My mistake, for some reason I thought you were suggesting `retalloc = retalloc + bufflen + 1;` I see now that I misread your comment. – torstenvl Dec 10 '18 at 04:06
  • Wouldn't it be simpler to have `char *buffer = malloc(BSZ);` do `fgets(buffer, ...)` then `buffer = realloc(...)` to a lower value (if relevant) based on the string length? (ie without using an intermediate variable, and adding 1k to the stack) – Déjà vu Dec 10 '18 at 05:29
  • You're right, there are other ways to do it that don't use as much stack. However, if I were willing to temporarily run with more heap allocated than necessary, I'd put in another optimization: doubling the allocated memory each time I run out of space, instead of just adding to it. Allocator calls are expensive. Anyway, if 1K on the stack is risky on your system, you can always reduce `BSZ`. – torstenvl Dec 10 '18 at 06:07