Link to home
Start Free TrialLog in
Avatar of dminh01
dminh01

asked on

C function question

hi there
I am learning C and one of the question in the book is asking:

write a c function good_echo that reads a line from standard input and write to standard output.It should work for any input line of abitrari length.You can use fgets but must make sure it does not overflow. Your code should check for error conditions and return when one is encounted.

Can you guys please help.

Thanks
SOLUTION
Avatar of abith
abith
Flag of India image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of dminh01
dminh01

ASKER

can u please implement that in C. I just started and wanted to know how to implement

thanks
Avatar of dminh01

ASKER

and i think it is just the screen argument that they mean by standard input / output .Is that right?
Avatar of F. Dominicus
The comment so abith do not help that much. He's right in principal but his suggestion does not fullfill the requirement about:
"should work for any input line of abitrari length."

Fgets just works in fixed sizes. So the whole stuff has to work something like this

Pseudo C
int SOME_LIMIT = 512;
char *out_string = malloc(SOME_LIMIT);
char buf[SOME_FIXED_LENGTH];

char *backspace_location;
char *pc = fgets (buf, sizeof(buf), stdin);
if (pc) {
    backspace_location = strchr(buf, "\n"),
    if (backspace_location) {
       /* now if it's directly in the string you have to see:
        was it the first call to fgets or has fgets called before because the string was not found
        one could e.g use the out_string == NULL to show that
    } else {
        /* ot '\n' in the string. So we have ti store buf away and run fgets again
       if (SOME_LIMIT > strlen(out_str) + sizeof(buf)) {
           strcpy(out_str, buf);
          /* save the buffer */
       } else {
          /* SOME_LIMIT is too low one has to reallocate the allocated memory */
         SOME_LIMIT = SOME_LIMIT * 2;
         char *n_string = realloc(out_str, SIZE_LIMIT);
         /* checking if realloc has succeeded */
        /* copy the buf into the newly allocates string */
}  ....
  /* loop to read another line till a '\n' can be found. ....


For me it seems the whole purpose of this excercise is learning to use malloc/realloc.


Regards
Friedrich
     
>> and i think it is just the screen argument that they mean by standard input / output .Is that right?
>>You can use fgets
there is confusion between these two :(

Friedrich:
i wonder, how you post the code for homeworks...!! :-|

>> 4) continue till reaching eof.
   means read ful file content, any number of lines...


>>can u please implement that in C. I just started and wanted to know how to implement
please post your code. definetely you will get support.

if there is any escape sequences, you dont need to worry about it, since its just ggoing to print. while printing you need to split strings based on \n and print line by line.
I do not posted the full code just the idea. And I surely do not have tried it really. It's just to give an idea.
Figuring out my code is punishment enought ;-)

Regards
Friedrich
Just to lift up some confusion :

>> does standard input  refer file or screen?

It could be either ... It is whatever the system has defined as standard input (stdin) for your application. On a desktop system, that's usually the keyboard. But if the application was started like this eg. :

        app < in.txt

then the standard input will (transparently) be the in.txt file.

In any case, you should not worry about what the standard input is exactly - only that it is the standard input ;)

Same for standard output (stdout).



Now, the trick is to use fgets to read input of a certain maximum length, then check the read string for a newline character ('\n'). If it isn't found, then read some more before continuing (you'll have to expand the buffer whenever needed !). Repeat this until a newline character is found, then just print the string to standard output, and start with the next line (in the same way).


        http://www.cplusplus.com/reference/clibrary/cstdio/fgets.html
Maybe it is easier to separate buffer allocation from reading.

You could do that by reading byte for byte calling fgetc and add each byte to a buffer using a function like that:

#define BUCKET_SIZE 512

char * addCharToBuf(char * buf, int * psiz, int* plen, char b)
{
      int siz = *psiz;
      int len = *plen;
      if (len+1 >= siz)
      {
               /* increase the siz and *psiz by adding BUCKET_SIZE */
               ...
               /* reallocate memory of the new siz */
               buf = (char*)realloc(...);
               /* check for buf == NULL and return NULL if so */
               ...
      }
      /* here the buffer is big enough to take the new byte */
      buf[len++] = b;
      /* increment the passed length in plen */
      ...
      return buf;
}

You can use the above function for the output buffer as well or print immediately as Infinity has suggested.

Regards, Alex

reading one character at a time is going to be quite slow though ... You can do something similar with fgets, but implement an addStringToBuf method instead ;)
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> I thought the same for many years until I made tests. The differences were not measurable for smaller files what was due to the internal buffering of the stream functions.

I don't know how and under what circumstances you tested it, but I just tried it here, and get the consistent result that the fgetc version is quite a bit slower than the fgets version :

For lines of 500 characters long :

        fgetc (100 lines) -> 0.0190 s
        fgets (100 lines) -> 0.0000 s
        fgetc (1000 lines) -> 0.0751 s
        fgets (1000 lines) -> 0.0100 s
        fgetc (10000 lines) -> 0.7982 s
        fgets (10000 lines) -> 0.1261 s
        fgetc (100000 lines) -> 7.7572 s
        fgets (100000 lines) -> 1.1306 s

(fgets is about 7 times faster)

Same test for lines of 100 characters long :

        fgetc (100 lines) -> 0.0040 s
        fgets (100 lines) -> 0.0010 s
        fgetc (1000 lines) -> 0.0300 s
        fgets (1000 lines) -> 0.0030 s
        fgetc (10000 lines) -> 0.2163 s
        fgets (10000 lines) -> 0.0241 s
        fgetc (100000 lines) -> 1.5131 s
        fgets (100000 lines) -> 0.3395 s
        fgetc (1000000 lines) -> 15.8949 s
        fgets (1000000 lines) -> 2.9182 s

(fgets is about 5 times faster)

and lines of 10 characters long :

        fgetc (1000 lines) -> 0.0010 s
        fgets (1000 lines) -> 0.0010 s
        fgetc (10000 lines) -> 0.0140 s
        fgets (10000 lines) -> 0.0030 s
        fgetc (100000 lines) -> 0.1452 s
        fgets (100000 lines) -> 0.0391 s
        fgetc (1000000 lines) -> 1.6053 s
        fgets (1000000 lines) -> 0.4186 s

(fgets is about 4 times faster)


NOTE : don't pay attention to the low time values - they're not accurate. All values are averages of 10 runs.


The relevant code used for both tests (to read one line) is :

    char *ptr = buf;
    while (((*ptr++ = (char) fgetc(fp)) != '\n') && (!feof(fp)));

versus :

    fgets(buf, 512, fp);
>>>> The differences were not measurable for smaller files
>>>> (fgets is about 4 times faster)

Hmmm. My tests have been some years ago ...

But I repeated it, using the prog below.

The file I read was the source code as well. But I made a copy so that the second attempt has no advantages. It has less than 100 lines what is your smallest, but I think as we discuss her a stream coming from stdin it is valid to do so. I let some unnecessary includes to not make it tiny. The file has about 1000 bytes. When testing in debug mode I got the result that fgetc has 15ms or 16ms (what is the resolution of GetTickCount) and fgets has 0. I turned to firstly do the fgets and had the same results. After building in release mode, things were changing. For 18 of twenty attempts I had 0 ms for both, there was one case where fgetc has 15ms but there was one case as well where fgets has 15ms. So, I don't think I have to take back the statement "that for smaller files the differences were not measurable".

BTW, the prog is pure C (as the thread was in C TA) but I am sure that my former test have been in C++, where the differences surely were not greater,  cause C++ compilers hardly will better optimize than C compilers.

Regards, Alex

P.S. I personally don't use fgetc nor fgets but read the whole file using one read or use getline function to read a line of arbitrary length to a C++ string class object. I do *not* make a recommendation using fgetc over fgets, I only said that it wouldn't matter for the issue discussed here in the thread. And that it may be easier if an arbitrary number of chars must be read.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fstream.h>
#include <windows.h>
 
 
int
main(int argc, char **argv)
{
    long l0, l1, l2, l3;
    int i;
    char* s;
    char buf[100000] = { 0 };

    FILE* file;

    file = fopen("c:\\sb\\test\\vc6test\\test2_copy.c", "rt");

    l2 = GetTickCount();
    while (!feof(file))
    {
        s = fgets(&buf[i], 512, file);
        if (s == NULL)
            break;
        s = strchr(&buf[i], '\n');
        i += (s == NULL)? 512 : (s - &buf[i]);
    }
    l3 = GetTickCount();
    fclose(file);

    memset(buf, 0, sizeof(buf));

    file = fopen("c:\\sb\\test\\vc6test\\test2.c", "rt");

    l0 = GetTickCount();
    i = 0;
    while (!feof(file))
        buf[i++] = (char)fgetc(file);
    l1 = GetTickCount();
    fclose(file);

    printf("fgetc = %d\n", l1 - l0);
    printf("fgets = %d\n", l3 - l2);

    system("pause");
    return 0;
}
 

>> I only said that it wouldn't matter for the issue discussed here in the thread.

Sure, I agree. But I don't see why you'd make it more complicated by using fgetc over fgets ... fgets is so much simpler to use when you need to read lines from a file (which is exactly what we have to do here).

Compare :

    char *ptr = buf;
    while (((*ptr++ = (char) fgetc(fp)) != '\n') && (!feof(fp)));

to :

    fgets(buf, 512, fp);

I know which one I pick, especially since the second is faster (best noticeable when processing larger files).

The fact that you can't measure the speed difference for smaller files does not mean that there is no speed difference (run the same code reading your small file a 1000 times, and you'll see the difference).


Anyway : again : I agree that for this question speed doesn't matter, so dminh01 can just ignore this part of the discussion ;)
>>>> fgets(buf, 512, fp);

If you look to my code (and to Kent's code above) you'll see that the fgets case requires more statements than the fgetc as you need to determine the bytes read. Also the AddCharToBuf is (slightly) easier than a AddStringToBuf what was my reason to choose fgetc here in that thread.
Others have implemented stuff like this:
http://cbfalconer.home.att.net/download/index.htm

reading char by char in an interactive program is surely more than fast enough. And it is definitly simpler.

Regards
Friedrich
>>>> while (((*ptr++ = (char) fgetc(fp)) != '\n') && (!feof(fp)));
That statement tries to use fgetc in order to get the functionality of a fgets. If you read the requirements you'll see that we don't need to care for lines but only to care for an arbitrary stream length. And that includes that we have to search for the '\n' in case of fgets what we can omit when using fgetc.

>>>> The fact that you can't measure the speed difference for smaller files does not mean that there is no speed difference
Agreed. But I didn't stated that. I only stated that the issue can be ignored for smaller files as it was not measurable. It is the same that I never would recommend using a bubble sort but of course sort efficency doesn't matter if I have to sort a few items only and integrating a library that provides a efficient sort algorithm and providing a suitable compare function is much more work than implementing a quick-and-dirty bubble sort.

>> That statement tries to use fgetc in order to get the functionality of a fgets.

Indeed ... to make the comparison fair (and because it was required for this question).


>> If you read the requirements you'll see that we don't need to care for lines

No ? And this ?

        "write a c function good_echo that reads a line"


>> Agreed. But I didn't stated that.

I wasn't trying to argue, Alex ... simply stating a fact for whoever reads this in the future.
>>>> "write a c function good_echo that reads a line"
Your point. To get a line echo from stdin I had to change my test prog to

    int i;
    char buf[100000] = { 0 };

    i = 0;
    while (!feof(stdin))
    {
        char c = (char)fgetc(stdin);
        buf[i++] = c;
        if (c == '\n')
        {
            printf("%s", buf);
            memset(buf, 0, sizeof(buf));
            i = 0;
        }
    }

what actually has to check for the '\n' (as you did in your while loop).

But as the 'line' can have an arbitrary length, the fgets case wasn't easier:

    int i, l;
    char buf[100000] = { 0 };
    char str[512];

    i = 0;
    while (!feof(stdin))
    {
        char* s = fgets(str, 512, stdin);
        if (s == NULL)
            break;
        s = strchr(str, '\n');
        l = (s == NULL)? sizeof(str) : s - str + 1;
        strncpy(&buf[i], str, l);
        printf("%s", buf);
        memset(buf, 0, sizeof(buf));
        i = 0;
    }

(And in Windows console I had to type CTRL+Z  + <ENTER>)


>>>> I wasn't trying to argue
Nor was I. I hope we can agree on that we both were right and both were wrong ;-)
>> >>>> I wasn't trying to argue
>> Nor was I. I hope we can agree on that we both were right and both were wrong ;-)

Sure we can :)