Geoff McLennan Geoff McLennan - 1 month ago 8
C Question

C: Passing files as parameters

I am having trouble passing a FILE pointer as a parameter into multiple functions.

I want to open the file in main, then pass it down into a function, which will write some things into it, then from that function pass it into another function. I can pass it from main to the first function no problem, but when I try to pass it to the second function it has a Segmentation fault.

My code looks something like this:

void firstFunction(FILE*);
void secondFunction(FILE*);

int main(void) {
FILE *fp;
if((fp = fopen("test.txt", "ab+")) == 0) {
return 1;
}
firstFunction(fp);
return 0;
}

void firstFunction(FILE *fp) {
fprintf(fp, "test"); /* Works */
secondFunction(fp); /* Causes Segmentation fault */
}

void secondFunction(FILE *fp) {
/* never reaches here */
fprintf(fp, "test2");
}


This pretty well explains where I'm at with this. I've tried passing in various different syntaxes, such as secondFunction(*fp), but with no success.

Whats going wrong? How come it works when going from main to firstFunction but not from first to secondFunction?

EDIT: Actual Code

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

#define LINESIZE 512

void runCommand(const char[], FILE*);
void append(const char[], FILE*);

int main(int argc, char * argv[]) {
char filename[LINESIZE];
FILE *fp;
char line[LINESIZE];

sscanf(argv[1], "%s", filename);

/* Open file for saving data */
if((fp = fopen(filename, "ab+")) == 0) {
perror("fopen");
return 1;
} else {
/* Reads lines from user */
while(fgets(line, LINESIZE, stdin)){
/* Omits empty lines */
if(strlen(line) > 1) {
runCommand(line, fp);
}
}
}

return 0;
}

/* Parses first word in input and executes proper command */
void runCommand(const char input[], FILE *fp) {
char param1[LINESIZE];

fprintf(fp, "test");
append(input, fp);

int r = sscanf(input, "%s", param1);

if (!strcmp(param1, "append")){
append(input, fp);
} else {
/* Error Handling */
}
}

void append(const char input[], FILE *fp) {
char command[7];
char fName[20];
char lName[20];
int score;

int r = sscanf(input, "%s%s%s%d", command, fName, lName, score);

if (r != 4) {
/* Error Handling */
}

fprintf(fp, "%s%s%d", fName, lName, score);
}

Answer
$ gcc -g -W -Wall -Wextra x.c -o x
x.c: In function 'append':
x.c:56:5: warning: format '%d' expects argument of type 'int *', but argument 6 has type 'int' [-Wformat=]
     int r = sscanf(input, "%s%s%s%d", command, fName, lName, score);
     ^
x.c:56:9: warning: 'score' is used uninitialized in this function [-Wuninitialized]
     int r = sscanf(input, "%s%s%s%d", command, fName, lName, score);
         ^
$ valgrind ./x test.txt 
==16814== Memcheck, a memory error detector
==16814== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==16814== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==16814== Command: ./x test.txt
==16814== 
a b c 123
==16814== Use of uninitialised value of size 8
==16814==    at 0x4E9B2D9: _IO_vfscanf (in /lib64/libc-2.20.so)
==16814==    by 0x4EA9BAB: __isoc99_vsscanf (in /lib64/libc-2.20.so)
==16814==    by 0x4EA9B26: __isoc99_sscanf (in /lib64/libc-2.20.so)
==16814==    by 0x400A4A: append (x.c:56)
==16814==    by 0x400993: runCommand (x.c:39)
==16814==    by 0x4008F9: main (x.c:26)

You're passing score in line 56 in your call to sscanf, but you should pass it's address. So add an ampersand:

int r = sscanf(input, "%s%s%s%d", command, fName, lName, &score);

After that, the error is gone and test.txt reads like this:

testbc123testcd23cd23

In C, arguments to functions are ALWAYS call by value. So if sscanf should write to the variable, passing the variable doesn't help. You actually have to pass it's address, so sscanf can write to it. That the other three variables (command, fName and lName) don't need an ampersand is because in C, you also cannot pass arrays. If you try to pass an array, indeed you're passing a pointer to it's first element. So the function prototypes

void runCommand(const char[], FILE*);
    void append(const char[], FILE*);

are actually missleading, because what they really mean is this:

void runCommand(const char *, FILE*);
    void append(const char *, FILE*);

and in your call to sscanf, the arrays "fall down" to pointers (so sscanf can actually write into their contents).

/edit: Slightly modified code to include some newlines in the fprintf statements (and a note where the call was) and this is the output of above's inputs:

runCommand: test
append: bc123
runCommand: test
append: cd23
append: cd23