0

I'm asked to read from user till a significance sign (delimiter), and put it in a character string. I should not ask how many lines/characters are expected nor waste memory space.

It's preferable that I don't use the built in packages in C. I'm learning from the scratch. So..

1st question: Should I use pointer or array of character?
Note that I don't know how long I'm going to read and I can't waste memory.

Here is what I did:

int main() {
    char s[100];
    int line = 0;
    int i = 0;
    printf("type ");
    printf("\n");
    scanf("%s", &s[i++]);

    while (s[i] != '000') {
        if (s[i] == '\n') line++;
        i++;
        scanf("%s", &s[i]);
    } //end while
    s[i] = '\0';
    printf("\n");
    printf("lines %d", line);
    printf("\n");
    int j;
    while (s[j] != '\0') {
        printf("%s", s[j]);
        j++;
    }
    return 0;
} //end main
little_birdie
  • 5,600
  • 3
  • 23
  • 28
voxic
  • 9
  • 5
  • *...I should not ask what lines...*. As in how long the input will be? – Haris Nov 10 '15 at 17:10
  • 1
    1st of all: Indent your code properly *without* using tabs, as it is unreadable for a common human. – alk Nov 10 '15 at 17:18
  • 1
    What a nice guy you are, @chux. – alk Nov 10 '15 at 17:20
  • @alk, i have done this many times, never got any compliment though. -_- – Haris Nov 10 '15 at 17:21
  • @Haris: Me neither, time to be polite ... :-) – alk Nov 10 '15 at 17:22
  • 1
    @alk, just a joke.. :D – Haris Nov 10 '15 at 17:23
  • 2
    Turn up the compiler's warning level to the maximum. Take warnings serious. – alk Nov 10 '15 at 17:24
  • 1
    This `scanf("%s", &s[i++]);` should make the compiler yell out a warning. – alk Nov 10 '15 at 17:25
  • 2
    You might like to learn how to use a debugger, as well as how to use a memory checker like Valgrind (https://valgrind.org). – alk Nov 10 '15 at 17:27
  • Coming to the point, the issue you mentioned is a classic one. To solve this issue people use many kinds of `dynamic memory allocations`. – Haris Nov 10 '15 at 17:29
  • 1
    Shame you can't use the c-library functions, `ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream);` was tailor made for your task. – David C. Rankin Nov 10 '15 at 17:34
  • I suppose this is question about `realloc` – fghj Nov 10 '15 at 17:37
  • I think the question has a serious flaw in that ALL I/O in C is via the library functions. – user3629249 Nov 10 '15 at 17:39
  • this line: `while (s[i] != '000') {` is nonsense, What is the code checking? The calls to `scanf()` are trying to read array of strings, ("%s") but what seems to be actually wanted is to read a single char ("%c") – user3629249 Nov 10 '15 at 17:45
  • this line: `printf("%s", s[j]);` seems to be trying to print the input, char by char, but the `%s` will try to print a whole string. Suggest: `printf("%c", s[j]);` which will print a single character. – user3629249 Nov 10 '15 at 17:49
  • The telling question is "Are you supposed to dynamically allocate memory to hold characters read?" or "Are you given a specific size that the total data will not exceed?" You must select one or the other in order to *save* each character read. While dynamic allocation will allow zero wasted memory, it looks beyond the scope of your assignment. Which is it? – David C. Rankin Nov 10 '15 at 17:49
  • @user1034749 yes it seems like I should use the library function thank you , but I was not familiar with it . – voxic Nov 10 '15 at 17:52
  • this pair of lines: ` int j; while (s[j] != '\0') {` has the flaw that `j` is not initialized. Suggest: ` for( int j = 0; s[j]; j++ ) {` which will loop until `s[j]` is the trailing NUL char ('\0') – user3629249 Nov 10 '15 at 17:53
  • @David C. Rankin yes joke on me . – voxic Nov 10 '15 at 17:53
  • @DavidC.Rankin. suggest making your comments about getdelim() into an answer to the OP can accept it. – user3629249 Nov 10 '15 at 18:01

2 Answers2

0

You should rename your question "how to read strings of unknown length"... Here is an example of one way to do it.. there are slightly more efficient ways but I tried to keep it simple.

This code will read characters from stdin, and store them into s, allocating memory as necessary.. up to any size, until it either reads delim.. set to be a string of three 0 characters in a row, reaches EOF (end of file), or hits the limit of memory allocation size on your system.

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

int main() {

    char c, *delim = "000";
    int i = 0, delimlen = strlen(delim);

    /* start out with 2 bytes of memory.. in reality the operating system */
    /* will allocate a lot more than that...                              */
    /* using calloc rather than malloc so that memory is initially nulled */
    char *s = calloc(2, 1);

    /* read characters one at a time from stdin */
    while((c = fgetc(stdin)) != EOF) {
        /* place character into the next position in the string array.. */
        /* and add 1 to i so it indexes the next position in the array  */
        s[i++] = c;
        /* add null terminator */
        s[i] = 0;

        /* did we get delimiter? */
        if(i >= delimlen && !strcmp(s+i-delimlen, delim))
            break;

        /* increase size of memory s points to, if necessary */
        s = realloc(s, i+2);
    }
    printf("we got input: %s", s);
    /* once you are TOTALLY done with s, free the memory so that it can be used again */
    free(s);
    exit(0);
}

Note that if your input is coming from the terminal (keyboard) not from a file or pipe, it will wait for you to hit "enter" before passing input to the program. So, you won't see your delimiter until then. If that is a problem, look at this:

How to avoid press enter with any getchar()

Community
  • 1
  • 1
little_birdie
  • 5,600
  • 3
  • 23
  • 28
  • Just a thought -- shouldn't your delimiter be an `int` instead of a string? – David C. Rankin Nov 10 '15 at 20:21
  • No.. delimiter (according to the original question) is a sequence of three '0' characters in a row.. which is what this code looks for. if it was an integer delim=0, then how would you know if you were looking for just a single digit 0, two digits, or three? – little_birdie Nov 10 '15 at 20:25
  • '\000' is the octal literal of a null character, yes. But that is not the delimiter I am looking for. I am looking for the ASCII SEQUENCE of three characters "000". I do not know why the terminating sequence is `"000"`, but that is what the asker of the question is asking for. – little_birdie Nov 10 '15 at 20:33
  • That is 100% OK, that's why I qualified my comment with *Just a thought...*, I just wanted to make sure you weren't chasing your tail. (p.s. look at `strstr` and `strpbrk` for functions that may help) – David C. Rankin Nov 10 '15 at 20:40
  • Thanks, yea no offense taken, all I do these days is python, but I am a C programmer at heart from way way back.. so these kinds of questions are fun :) So many different ways to go about it.. fun to toss ideas around. It is an odd terminating sequence for sure, but since the question-asker is learning perhaps he will end up with a different scheme in the end. – little_birdie Nov 10 '15 at 20:45
  • all above answers are waay advanced , I appreciated time taken to help me but I cant seem to sort out my problem yet. I know I need to use malloc , but malloc needs a size which I should not ask for , so there I am stuck . – voxic Nov 10 '15 at 23:32
  • My answer uses malloc.. actually calloc which is a variation on malloc. The key to varying the size automatically is to use realloc to resize the memory as the input gets bigger. – little_birdie Nov 10 '15 at 23:34
  • It really does not get much simpler than this.. why don't you ask if you don't understand something specific and I, David or someone else will explain... Or compile my code.. try it.. it works.. then try to go through it line by line, read the man pages for calloc and realloc.. and try to understand why it works. – little_birdie Nov 10 '15 at 23:44
  • It would be nice if you would accept my answer.. which totally works and is right.. even if you can't understand it.. I spent a good amount of time writing it for you. Or if you like Davids answer then accept that. Not accepting answers isn't too cool. – little_birdie Nov 11 '15 at 18:28
0

While getdelim is custom made for this situation, the goal of not using the prefab function for a learning experience in this situation is a very good choice. If I understand the task, you want to read all data from a file (or stdin) and if given an alternate delimiter (something other than the normal '\n') use that character is the line-end for the purpose of separating and counting the lines.

To handle the input, you need to do nothing but read/store each character (that is not a delimiter) in an array (we will use a static array below for purpose of the example, but you can allocate/realloc if desired). If a new alternative delimiter is read, then terminate the line, increment the line count, and move to the next character.

A basic approach would be something like:

#include <stdio.h>

#define MAXC 512

int main (int argc, char **argv) {

    int delim = argc > 1 ? *argv[1] : '\n';
    char s[MAXC] = {0};
    int c;
    size_t nchr = 0, lines = 0;

    /* for each char in input (stdin) */
    while ((c = getchar()) != EOF) {

        if (c == delim) {   /* if delim, store newline */
            s[nchr++] = '\n';
            lines++;
        }
        else if (c != '\n') /* store char */
            s[nchr++] = c;

        /* check (MAX - 2) to allow protection - see below */
        if (nchr == MAXC - 2) {
            fprintf (stderr, "warning: MAXC reached.\n");
            break;
        }
    }
    /* protect against no terminating delim */
    if (s[nchr-1] != delim) {
        s[nchr++] = '\n';
        lines++;
    }
    /* null-terminate */
    s[nchr] = 0;

    printf ("\nThere were '%zu' lines:\n\n", lines);
    printf ("%s\n", s);

    return 0;
}

A sample input file will have both normal line-ends as well as alternative delimiters for testing:

Example Input

$ cat dat/captnjack_delim.txt
This is +a tale+
Of+ Captain Jack Sparrow+
A Pirate So Brave
On the +Seven Seas.

Example Output

using default '\n' as delim

$ ./bin/getchar_delim <dat/captnjack_delim.txt

There were '4' lines:

This is +a tale+
Of+ Captain Jack Sparrow+
A Pirate So Brave
On the +Seven Seas.

using '+' as delim

$ ./bin/getchar_delim + <dat/captnjack_delim.txt

There were '6' lines:

This is
a tale
Of
 Captain Jack Sparrow
A Pirate So BraveOn the
Seven Seas.

Note: you can also tweak the conditional test to handle '\n' and ' ' substitution to fit your needs. If you are reading from a file, your will use fgetc instead of getchar, etc.. Let me know if you want a getdelim example as well.

Using getdelim

The same thing can be accomplished using getdelim with dynamic memory allocation. Note: initially pointers for 2 lines are allocated (#define MAXL 2) which will force reallocation of the lines to handle any lines over 2. In practice set this to a reasonably anticipated number of lines. (you want to minimize the number of allocations/realloations if possible. you can also set to 1 to force allocation of a new line each time, it's just less efficient that way)

The two macros included at the beginning just do error checking on calloc allocation and remove any trailing carriage returns newlines or delimiters. (you can move these to functions if you prefer)

Note: due to the way getdelim works, delimiters like This is +a tale+ will cause an initial and embedded newline to be included as part of the following line. You can remove them if you choose, but DO NOT alter the starting address of s since it is dynamically allocated by getdelim. Use an additional pointer and temp string instead.

A short example using the same data would be:

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

#define MAXL 2

/* calloc with error check macro */
#define xcalloc(nmemb, size)       \
({  void *memptr = calloc((size_t)nmemb, (size_t)size);    \
    if (!memptr) {          \
        fprintf(stderr, "error: virtual memory exhausted.\n");  \
        exit(EXIT_FAILURE); \
    }       \
    memptr; \
})

/* remove trailing '\r' '\n' and delim macro */
#define rmcrlfdelim(str, delim)  \
({  char *p = (char *)str;  \
    int d = (int)delim; \
    for (; *p; p++) {}  \
    p--;    \
    for (; p > str && (*p == '\n' || *p == '\r' || *p == d); p--) \
        *p = 0, nchr--;  \
})

int main (int argc, char **argv) {

    int delim = argc > 1 ? *argv[1] : '\n';
    char **lines = NULL;
    char *s = NULL;
    ssize_t nchr = 0;
    size_t n = 0;
    size_t nlines = 0;
    size_t maxl = MAXL;
    size_t i = 0;

    lines = xcalloc (MAXL, sizeof *lines);

    /* for each segment of input (stdin) */
    while ((nchr = getdelim (&s, &n, delim, stdin)) != -1) {

        rmcrlfdelim (s, delim);         /* remove trailing \n \r delim  */
        lines[nlines++] = strdup (s);   /* allocate/copy s to lines     */

        if (nlines == maxl) {   /* realloc if needed */

            void *tmp = realloc (lines, maxl * 2 * sizeof *lines);
            if (!tmp) {
                fprintf (stderr, "error: realloc - memory exhausted.\n");
                exit (EXIT_FAILURE);
            }
            lines = (char **)tmp; /* below - set new pointers NULL */
            memset (lines + maxl, 0, maxl * sizeof *lines);
            maxl *= 2;
        }
    }
    free (s);   /* free mem allocated by getdelim */

    printf ("\nThere were '%zu' lines:\n\n", nlines);
    for (i = 0; i < nlines; i++)
        printf ("%s\n", lines[i]);

    for (i = 0; i < nlines; i++)    /* free allocated memory */
        free (lines[i]);
    free (lines);

    return 0;
}

Example Output

using default '\n' as delim

$ ./bin/getdelim <dat/captnjack_delim.txt

There were '4' lines:

This is +a tale+
Of+ Captain Jack Sparrow+
A Pirate So Brave
On the +Seven Seas.

using '+' as delim

$ ./bin/getdelim + <dat/captnjack_delim.txt

There were '6' lines:

This is
a tale

Of
 Captain Jack Sparrow

A Pirate So Brave
On the
Seven Seas.

(yes, that is really 6 lines -- with embedded newlines)

lines[ 0] : This is
lines[ 1] : a tale
lines[ 2] :
Of
lines[ 3] :  Captain Jack Sparrow
lines[ 4] :
A Pirate So Brave
On the
lines[ 5] : Seven Seas.
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85