Solved

C function question

Posted on 2007-12-03
19
864 Views
Last Modified: 2008-02-01
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
0
Comment
Question by:dminh01
  • 6
  • 6
  • 3
  • +2
19 Comments
 
LVL 5

Assisted Solution

by:abith
abith earned 200 total points
Comment Utility
does standard input  refer file or screen?

since you have mentioned fgets, i assume that its related to file process.
if so, is it file copy process ? using fgets is mandatory? anyway here is the way using fgets
1 ) open two files, using fopen which returns handler
2 ) read file, using fgets which requires buffer, size and handler
2 a ) check the return value of fgets, NULL says error or eof. use ferror to find whether error is encountered. if so, display the error
3) write buffer, using  fwrite into 2nd handler.
4) continue till reaching eof.
5) close both handlers using fclose
0
 

Author Comment

by:dminh01
Comment Utility
can u please implement that in C. I just started and wanted to know how to implement

thanks
0
 

Author Comment

by:dminh01
Comment Utility
and i think it is just the screen argument that they mean by standard input / output .Is that right?
0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
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
     
0
 
LVL 5

Expert Comment

by:abith
Comment Utility
>> 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.
0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
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
0
 
LVL 53

Expert Comment

by:Infinity08
Comment Utility
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
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
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

0
 
LVL 53

Expert Comment

by:Infinity08
Comment Utility
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 ;)
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 300 total points
Comment Utility
>>>> reading one character at a time is going to be quite slow though ...
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.

The idea using an addStringToBuf is not bad ;-)  but you might consider using a fread rather than a fgets as there is no advantage when reading the input line by line instead of (equally-sized) buffer by buffer. The addStringToBuf prototype is

    char * addStringToBuf(char * buf, int * psiz, int* plen, const char * s, int slen);

You can omit the slen if you correctly zero-terminate any string to add. Then the function can determine the length by strlen.  The main differences to AddCharToBuf are

        /* if (len + 1 >= siz) */
        while (len + slen >= siz)

and

        /* buf[len++] = b;  */
       strncpy(&buf[len], s, slen);
       len += slen;

Note, you could turn the 'while (len + slen >= siz) ' to an 'if (len + slen >= siz)' statement in case the passed strings always have a size less than BUCKET_SIZE. But actually the while would handle all cases and you should let it if you understand the reason why I used it.        

Regards, Alex


0
 
LVL 53

Expert Comment

by:Infinity08
Comment Utility
>> 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);
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> 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;
}
 

0
 
LVL 53

Expert Comment

by:Infinity08
Comment Utility
>> 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 ;)
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> 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.
0
 
LVL 24

Expert Comment

by:fridom
Comment Utility
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
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> 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.

0
 
LVL 53

Expert Comment

by:Infinity08
Comment Utility
>> 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.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
Comment Utility
>>>> "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 ;-)
0
 
LVL 53

Expert Comment

by:Infinity08
Comment Utility
>> >>>> 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 :)
0

Featured Post

Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

Join & Write a Comment

Suggested Solutions

Have you thought about creating an iPhone application (app), but didn't even know where to get started? Here's how: ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ Important pre-programming comments: I’ve never tri…
Preface I don't like visual development tools that are supposed to write a program for me. Even if it is Xcode and I can use Interface Builder. Yes, it is a perfect tool and has helped me a lot, mainly, in the beginning, when my programs were small…
The viewer will learn how to pass data into a function in C++. This is one step further in using functions. Instead of only printing text onto the console, the function will be able to perform calculations with argumentents given by the user.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now