1

I've been messing with this really simple C function to return the contents of a text file as a string for quite a while now, but no matter what I do, it doesn't work.

#include<stdio.h>

char* getContents(char filename[]) {
    FILE *fp;
    char text[1000];
    fp = fopen(filename, "r");
    int i = 0;
    while (feof(fp)) {
        text[i] = fgetc(fp);
        i++;
    } text[i] = '\0';
    return text;
}

int main() {
    printf("%s", getContents("text.txt));
}

I've looked at several other Stack Overflow discussions with similar premises to no avail — pretty much no combinations of *s in different places makes this work, I always get either "function returns address of local variable" or "return makes pointer from integer without a cast" errors. How can I make this function return the string like it should?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • You need to dynamically allocate the memory (e.g.: using `malloc`) – UnholySheep Feb 08 '21 at 21:59
  • 1
    _strings_ are arrays of data. Arrays are not returnable in C. `char*` is a _pointer_, not an _array_. Instead of "How can I make this function return the string like it should?", consider returning a pointer to allocated data. – chux - Reinstate Monica Feb 08 '21 at 22:00
  • Does this answer your question? [Returning an array using C](https://stackoverflow.com/questions/11656532/returning-an-array-using-c) – Stephen Newell Feb 08 '21 at 22:01
  • 2
    _"...return the string like it should?"_ ; you mean like you _want_. Strings are just `char` arrays and arrays are not first-class types in C - so not "like it should", because it shouldn't. – Clifford Feb 08 '21 at 22:02
  • 1
    Should be `int i=0, c; while ((c=fgetc(fp)) != EOF) {text[i]=c; i++}` Using `feof(fp)` is wrong 99.9% of the time. And see the other comments about why `return text;` is wrong. – user3386109 Feb 08 '21 at 22:02
  • 2
    [Why is “while ( !feof (file) )” always wrong?](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – Andreas Wenzel Feb 08 '21 at 22:04
  • 2
    @James Browning Strictly speaking functions may return strings.:) But they may not return pointers to local objects with automatic storage duration.:) – Vlad from Moscow Feb 08 '21 at 22:04

5 Answers5

1

The function getContents has several problems.

char * getContents(char filename[]) {
    FILE *fp;
    char text[1000];
    fp = fopen(filename, "r");
    int i = 0;
    while (feof(fp)) {
        text[i] = fgetc(fp);
        i++;
    } text[i] = '\0';
    return text;
}

The main problem is that you are trying to return pointer to the first element of the local array text with automatic storage duration that will not be alive after exiting the function.

So the pointer will be invalid.

You could for example define the array as having static storage duration

static char text[1000];

In this case the function may return pointer to the first element of the array like

    return text;

because the array will be alive after exiting the function.

But it is better to allocate dynamically memory for the array like

char *text = malloc( 1000 );

Another problem is that you should check whether the specified file was opened successfully.

The condition in this while loop

    while (feof(fp)) {
        text[i] = fgetc(fp);
        i++;
    } 

is not correct. End of the file can occur in this statement

        text[i] = fgetc(fp);

In this case you will write in the array the value of the expression (char)EOF.

Also you need to check whether you are not writing outside the allocated dynamically array.

The loop can look like

    size_t i = 0;

    for ( int c; i + 1 < 1000 && ( c = fgetc( fp ) ) != EOF; i++ )
    {
        text[i] = c;
    } 
    text[i] = '\0';

Also you should close the file before exiting the function

    fclose( fp );

The function can look for example the following way

char * getContents( const char filename[]) 
{
    enum { N = 1000 };
    char *text = NULL;

    FILE *fp = fopen( filename, "r" );

    if ( fp != NULL )
    {
        text = malloc( N * sizeof( char ) );
        
        if ( text == NULL )
        {
            fclose( fp );
        }
        else
        {
            size_t i = 0;
            for ( int c; i + 1 < N && ( c = fgetc( fp ) ) != EOF; i++ )
            {
                text[i] = c;
            } 
            text[i++] = '\0';
            
            fclose( fp );
            
            char *tmp = realloc( text, i * sizeof( char ) );
            
            if ( tmp != NULL ) text = tmp;
        }
    }
    
    return text;
}

If the file was not opened successfully of if the memory for the array was not allocated the function returns a null pointer.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • `while(feof())` ? You have duplicated an error in the original code. – Clifford Feb 08 '21 at 22:21
  • @Clifford Wher did I duplicate? See the suggested by me loop. – Vlad from Moscow Feb 08 '21 at 22:22
  • My apologies - you duplicated the entire code - that was not intended to be your solution. – Clifford Feb 08 '21 at 22:24
  • The question is very specific - about returning a string. It is perhaps unfortunate that the code example it has a number of distracting issues, but they need not be addresses in an answer specifically I think. – Clifford Feb 08 '21 at 22:37
  • 1
    @Clifford If they will not be addressed then somebody who will read the question and answers will wonder why they were not addressed.:) – Vlad from Moscow Feb 08 '21 at 22:39
  • Wouldn't `i + 1 < N` be clearer written as `(i + 1) < N` – Ed Heal Feb 08 '21 at 22:41
  • @EdHeal It's a matter of taste. – Vlad from Moscow Feb 08 '21 at 22:43
  • 1
    @chux-ReinstateMonica I updated the code.:) – Vlad from Moscow Feb 08 '21 at 22:52
  • @VladfromMoscow You think? If I came to this question because I was searching for "returning strings in C", I'd struggle to see the solution amongst the confounding issues which are mostly issues that the OP would discover in debug were he able to get the code to compile. They are adequately dealt with in comments. It is a matter of opinion on SO "style" I guess and I can see both sides, but the answer can get a bit "TL;DR". Search my answers and no doubt I will have contradicted myself somewhere. – Clifford Feb 08 '21 at 23:21
0

Arrays of any type are not first class types and when passed to or from a function, they are passed by reference. There are a number of patterns to overcome this:

  • Pass in a pointer to an array in the caller's scope - the most idiomatic, and deterministic method.
  • Make the array text static - you can then return a pointer as you are already doing. This has re-entrancy issues and you don't have a pointer to unique string for each call. So if the function is called a second time the pointer returned by the first will point to content read by the second.
  • Wrap the array in a struct and return the struct - this is involves a mem-copy and not particularly efficient in memory or time terms.
  • Dynamically allocate the memory within the function and return a pointer - a recipe for memory leaks and non-deterministic performance, overall most often a bad idea.

I'd only recommend the first solution:

#include <stdio.h>

char* getContents( const char* filename, char* content, size_t maxlen ) 
{
    FILE* fp = fopen( filename, "r" ) ;
    
    // Write up to maxlen - 1 characters from file to `content`
    // then append nul terminator
    ...

    return content ;
}

int main() 
{
    char text[1000] = {0} ;
    printf("%s\n", getContents( "text.txt", 
                                text, 
                                sizeof(text) ) ) ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Note that the body of `getContents()` in the original question contains other issues that are not related to the question. I have deliberately avoided addressing these as the question specific and not "what is wrong with this code?". – Clifford Feb 08 '21 at 22:48
  • Whether arrays are first class types or not is irrelevant, and whether they are passed or returned by reference is not the issue. This answer fails to state what the problem is: returning a pointer to an object whose lifetime ends when the function returns. Observe that returning a static or allocated array would work, so the problem is not about properties of an array. Observe that returning a pointer to a non-array automatic object would fail (have undefined behavior), so the problem is not particular to arrays: It is about the lifetime of an object, not about arrays. – Eric Postpischil Feb 08 '21 at 22:48
  • @EricPostpischil : _"Why won't this C function return strings?"_ - I think the nature of arrays and how they behave when passed to or from a function is exactly relevant. The OP is under the impression that he is returning a string (in an char array), and he is not. It is clear in the question that the code is an example of an attempt to return a string, he mentions two different errors he has seen, so clearly this is not the only attempt. Since the code won't even compile, it is clearly not real code, so I have largely ignored it. Returning a static - thanks, I'll add that as a solution. – Clifford Feb 08 '21 at 23:04
  • @EricPostpischil - it is (also) as you say about the lifetime in the OPs example, but I am seeing that as a flawed attempt to return a string and secondary to the primary question, not actually the problem in hand. As I said the question is not "why does this code not work?" It is "Why won't this C function return strings?" – Clifford Feb 08 '21 at 23:08
0

There are multiple problems:

  • your loop exits immediately: while (feof(fp)). Testing for end of file should be done by comparing the return value of fgetc() with EOF.
  • you return the address of a local array with automatic storage: it is discarded as soon as the function returns. Make this array static or better allocate a string from the heap.
  • you do not close the input file.

Here is a modified version:

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

char *getContents(char filename[]) {
    char text[1000];
    int i, c;
    FILE *fp = fopen(filename, "r");
    if (fp == NULL)
        return NULL;
    for (i = 0; i < sizeof(text) - 1 && ((c = fgetc(fp)) != EOF; i++) {
        text[i] = c;
    }
    text[i] = '\0';
    fclose(fp);
    return strdup(text);
}

int main() {
    char *p = getContents("text.txt");
    if (p != NULL) {
        printf("%s\n", p);
        free(p);
    }
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    Your second point is mentioned in the question, and is the problem he is seeking to solve, the other problems while real, and not what is being asked and are comments rather the answers. The function gives no clues to the caller that the pointer returned is to dynamically allocated memory - overall this solution is a bad idea. – Clifford Feb 08 '21 at 22:16
-1

You cannot return variable sized stuffs in C. You can allocate it on the heap or return a fixed size thing.

To get the fixed sized return past the compiler do

struct {
    char data[1000];
} text;

struct text getContents(char filename[]) {
    struct text text;

If you'd rather do dynamic, there's a perfectly good example on Stephen Newell's proposed dupe target.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 1
    A pointer, which is what is being returned, *is* a "fixed size thing". – Scott Hunter Feb 08 '21 at 22:05
  • 1
    @ScottHunter: Considering the rest of the code in the question he's trying to return `char[]`. – Joshua Feb 08 '21 at 22:35
  • @Joshua: So? it is a fixed-size array, and whether it is variable length or fixed length is not relevant to the problem, which is that the code attempts to return a pointer to an object whose lifetime ends when the function returns. – Eric Postpischil Feb 08 '21 at 22:45
  • @EricPostpischil: And returning a fixed sized array doesn't compile for reasons that escape me, but wrapping the array in a struct suddenly starts working. – Joshua Feb 08 '21 at 22:47
  • 1
    The code and solution are valid, but the explanation is confusing. It is not a matter of "getting it past the compiler" - it is not a "trick", it is by language definition. And in the question the `text[]` array is a fixed length (the string withing it is not, but that is not the problem). In C an array is not a first-class data type, and a string is not a data type of any kind. – Clifford Feb 08 '21 at 22:55
  • @Clifford: I find myself believing that OP isn't ready for pointers yet; if OP where to say what he thinks a problem is with this answer or doesn't understand I'd happily fix it; until then I'd be guessing what to tell him. – Joshua Feb 09 '21 at 00:10
  • @Joshua Pointers are not an advanced topic in C. Some uses of pointers may be, but this is not one of them. You cannot reasonably use this non-idiomatic solution for all arrays you might want to pass around. It is unsustainable. – Clifford Feb 09 '21 at 07:27
  • @Clifford: I am well aware of this. Question reads like first semester C. Pointers are about 3/4 of the way through the class and this will last until the appointed time. – Joshua Feb 09 '21 at 16:10
  • In K&R 2nd.ed it is Ch.5 right after Ch4 Functions & Program Structure. The originators of the language seemed to think that it was important enough to cover right after understanding functions and program structure which clearly he does. This is _before_ Ch.6 _Structures_ BTW ;-) – Clifford Feb 09 '21 at 16:36
-1

A '"' missing in your printf:

int main() {
    printf("%s", getContents("text.txt"));
}
  • That is not an answer to the question. The code in the question has a number of flaws which distract from the question being asked. Address them in comments, or answer the question and mention the other issues. – Clifford Feb 08 '21 at 23:28