C: pointers mixup - c

#include<stdio.h>
#include<stdlib.h>
typedef struct{
char cStdName[50];
int nStdNum;
char cStdClass[4];
float dStdAvg;
}student;
student* students;
int cmp(const void* a, const void* b);
void main() {
int num = 0,i=0;
FILE *f;
printf("Number of students:");
scanf("%d", &num);
students = (student*)malloc(num*sizeof(student));
for(i=0;i<num;++i){
student* ptr = students+i*sizeof(student);
printf("Name:");
scanf("%s", ptr->cStdName);
printf("Num");
scanf("%d", &ptr->nStdNum);
printf("Class:");
scanf("%s", ptr->cStdClass);
printf("Grade:");
scanf("%f", &ptr->dStdAvg);
}
f = fopen("bin.bin","wb");
fwrite(&num,sizeof(int),1,f);
fwrite(students,sizeof(student),num,f);
fclose(f);
system("pause");
}
This is supposed to output the number of students and all the structure 'array' in a binary file and it works with 1 student. But when I add >=2 people, the file looks like this:
http://i.imgur.com/LgL8fUa.png
If I add only 1 student, there is still some of this Windows path nonsense:
http://i.imgur.com/s7fm9Uv.png
It is OK though, the program that reads the file ignores everything after the NULL(I mean, for the first char array).
I think the problem is somewhere in the for() loop and the pointer juggling but I can't tell where.

student* ptr = students + i * sizeof(student);
In C, pointer arithmetic already includes sizeof(student). You will read past the end of your arrray.
student* ptr = students + i;
However, you'll notice accessing to ptr is the same as accessing to students[i].

There are a few issues with your code:
First of all, as Kirilenko said, you should use students[i] in your code. In your case, students + i * sizeof(student) goes out of bounds.
It's never a good idea to use fwrite with an array of structs. That's because the compiler may add some space between the members of a struct (padding), which means that when you pass an array of structs to fwrite, the padding bytes (which will contain garbage) will be printed.
The same also applies to the char array members in your struct. All unused bytes will contain garbage, which will be printed when you use fwrite. It's better to use strlen to determine how many read characters each char array contains.
Here's how I'd write the students' array to a file:
void write(students* array, int len, FILE* out)
{
int i;
fwrite(len, 1, sizeof(len), out);
for (i = 0; i < len; i++){
fwrite(array[i]->cStdName, 1, strlen(array[i]->cStdName), out);
fwrite(array[i]->nStdNum, 1, sizeof(array[i]->nStdNum), out);
fwrite(array[i]->cStdClass, 1, strlen(array[i]->cStdClass), out);
fwrite(array[i]->dStdAvg, 1, sizeof(array[i]->dStdAvg), out);
}
}

Kirilenko's answer should solve your immediate problem, but there are errors ranging from trivial to severe on nearly every line of this program. Depending on what your larger goal is, you might not need to fix all of them, but I'm going to write them all down anyway, to illustrate the size of the iceberg.
void main() {
int main(void). The int is an absolute requirement. In C (not C++) writing () for a function argument list means the arguments are unspecified, not that there are no arguments; technically you can get away with it here because this is a function definition, but it's bad style.
The opening curly brace of a function definition always goes on a line by itself, even if all other opening braces are cuddled.
int num = 0,i=0;
Inconsistent spacing. Initializations are unnecessary.
printf("Number of students:");
Some style guides prefer fputs("Number of students", stdout); when you're not using printf's formatting capabilities. But some compilers can do the transformation for you, and it's not a big deal.
scanf("%d", &num);
Never use scanf, fscanf, or sscanf, because:
Numeric overflow triggers undefined behavior. The C runtime is allowed to crash your program just because someone typed too many digits.
Some format specifiers (notably %s, which you use later in this program!) are unsafe in exactly the same way gets is unsafe, i.e. they will cheerfully write past the end of the provided buffer and crash your program (this particular program doesn't look security-sensitive to me, but one should always code as if one's programs are at least somewhat dangerous that way).
They make it extremely difficult to handle malformed input correctly.
The correct way to read a single nonnegative number from the user is like this:
unsigned long getul(void)
{
char buf[80], *endp;
unsigned long val;
for (;;) {
fgets(buf, 80, stdin);
val = strtoul(buf, &endp, 10);
if (endp != buf && *endp == '\n')
return val;
if (buf[strlen(buf)] != '\n')
while (getchar() != '\n')
/* discard */;
fprintf(stderr, "*** Enter one nonnegative integer, smaller than %lu.\n",
ULONG_MAX);
}
}
You can get away with a fixed-size buffer here because nobody's ULONG_MAX is so large that it doesn't fit in 80 characters.
students = (student*)malloc(num*sizeof(student));
You should use calloc here, so that when you go to write your structures to disk, they aren't full of junk. (Or write custom serializers as suggested by Alexandros, that will also avoid the problem.)
for(i=0;i<num;++i){
Preferred style is for (i = 0; i < num; i++) {. In addition to the spacing, use i++ instead of ++i so that i appears in the same position in all three expressions; this makes it easier to read.
student* ptr = students+i*sizeof(student);
student* ptr = students + i; as discussed elsewhere.
printf("Name:");
scanf("%s", ptr->cStdName);
See comments above. You want another helper function, like so:
void getstr(char *buf, size_t len)
{
size_t n;
for (;;) {
fgets(buf, len, stdin);
n = strlen(buf);
if (n < len && buf[n] == '\n') {
memset(buf+n, 0, len-n);
return;
}
while (getchar() != '\n')
/* discard */;
fprintf(stderr, "*** Enter no more than %lu characters.",
(unsigned long)(len-1));
}
}
...
scanf("%f", &ptr->dStdAvg);
And here you need another helper function. getf is exactly the same as getul except it uses strtod, and of course the error message is a little different.
f = fopen("bin.bin","wb");
Isn't that an awfully generic file name? There should probably be a way for the user to specify it.
fwrite(&num,sizeof(int),1,f);
Your file format needs a magic number.
fwrite(students,sizeof(student),num,f);
You are writing binary data to disk in CPU-endian order. That may not be a problem for this application, but be aware that you might have a cross-platform compatibility headache down the road. (Personally, for what it looks like you're doing, I would use a textual serialization such as JSON, or a simple no-daemon database such as sqlite.) See Alexandros' answer for more potential problems with this file format.
It's rarely a problem when writing to files on disk, and this isn't the sort of program whose output gets piped somewhere, but still, I'm'a mention that fwrite does not guarantee to write all the data you provide it. Technically you have to call fwrite in a loop like this:
size_t r, n = sizeof(student) * num;
char *p = (char *)students;
while (n > 0) {
r = fwrite(p, 1, n, f);
if (r == 0) break; /* write error */
n -= r;
p += r;
}
For this to work correctly you must do the multiplication yourself and pass 1 for the second argument to fwrite; otherwise a short write may end in the middle of an "element of data" and you have no way of knowing that this has happened.
fclose(f);
Check for write errors before closing the file.
if (ferror(f) || fclose(f)) {
perror("bin.bin");
return 1; /* unsuccessful exit */
}
...
system("pause");
Just return 0. Programs that make you hit a key to exit are batch-unfriendly.

The pointer assignment can be outside for loop once and you can increment the pointer at the end of for loop. Try this.
Ok. Here is an attempt to explain what is happening, the ptr is pointing to first element in students list of struct types and when you increment the ptr at the end of the for loop, it points to the next student in the list.
---
students = (student*)malloc(num*sizeof(student));
student* ptr = students;
for(i=0;i<num;++i){
printf("Name:");
----
----
ptr++;
}

Related

How do I read multiple floats from one line of a file

I have the following code which reads from a given input file into and then into struct I have made.
OFFFile ReadOFFFile(OFFFile fileData, FILE* srcFile)
{
int nvert, nfaces;
fscanf(srcFile, "%s\n");
fscanf(srcFile, "%d %d %s\n", &nvert, &nfaces);
fileData.nvert = nvert;
fileData.nfaces = nfaces;
fileData.vertices = (int *) malloc(fileData.nvert * sizeof(float));
fileData.triFaces = (int *) malloc(fileData.nfaces * sizeof(int));
// Print to check correct size was allocated
printf("%d\n", (fileData.nvert * sizeof(float)));
printf("%d\n", (fileData.nfaces * sizeof(int)));
int i;
float ftemp1, ftemp2, ftemp3;
int itemp1, itemp2, itemp3;
fscanf(srcFile, "%f", &ftemp1);
printf("%lf", ftemp1);
fscanf(srcFile, "%f", &ftemp2);
// fscanf(srcFile, " %lf", &ftemp3);
/* for (i = 0; i < nvert; ++i)
{
fscanf(srcFile, "%f %f %f\n", &ftemp1, &ftemp2, &ftemp3);
fileData.vertices[i].x = ftemp1;
fileData.vertices[i].y = ftemp2;
fileData.vertices[i].z = ftemp3;
}
*/
return(fileData);
}
The problem I am having is with the whole last section that is currently in quotes (The 2 fscanf lines above it are me attempting to test). If I have just one float being read it works fine, but when I add the second or third the whole function fails to even run, although it still compiles. I believe it to be caused by the negative sign in the input, but I don't know how I can fix it.
The data is in the form
OFF
4000 7000 0
0.8267261981964111 -1.8508968353271484 0.6781123280525208
0.7865174412727356 -1.8490413427352905 0.7289819121360779
With the floats continuing on for 4000 lines (hence for loop). These are the structs I have made
typedef struct
{
float x;
float y;
float z;
} Point3D;
typedef struct
{
int face1;
int face2;
int face3;
} triFace;
typedef struct
{
int nvert;
int nfaces;
Point3D *vertices;
triFace *triFaces;
} OFFFile;
Text dump of another file with a lot less lines, also does not work in the for loop. Only using this for testing. https://justpaste.it/9ohcc
Your main problem is the first line in the readOFFFile function:
fscanf(srcFile, "%s\n");
This tries to read a string (presumably the string OFF on the first line of the file), but you don't give fscanf any place to store the string, so it crashes. (As an aside, your compiler really should have warned you about this problem. If it didn't, it's old-fashioned, and there are lots of easy mistakes that it's probably not going to warn you about, and learning C is going to be much harder than it ought to be. Or perhaps you just need to find an option flag or checkbox to enable more warnings.)
You can tell fscanf to read and discard something, without storing it anywhere, using the * modifier. Here's a modified version of your program, that works for me.
void ReadOFFFile(OFFFile *fileData, FILE* srcFile)
{
fscanf(srcFile, "%*s");
if(fscanf(srcFile, "%d %d %*s", &fileData->nvert, &fileData->nfaces) != 2) {
exit(1);
}
fileData->vertices = malloc(fileData->nvert * sizeof(Point3D));
fileData->triFaces = malloc(fileData->nfaces * sizeof(triFace));
int i;
for (i = 0; i < fileData->nvert; ++i)
{
if(fscanf(srcFile, "%f %f %f", &fileData->vertices[i].x,
&fileData->vertices[i].y,
&fileData->vertices[i].z) != 3) {
exit(1);
}
}
}
I have made a few other changes. The other fscanf call, that reads three values but only stores two, also needs a * modifier. I check the return value of fscanf to catch errors (via a crude exit) if the input is not as expected. I got rid of the \n characters in the fscanf calls, since they're not necessary, and potentially misleading. I got rid of some unnecessary temporary variables, and I had the readOFFFile function accept a pointer to an OFFFile structure to fill in, rather than passing and returning it.
Here is the main program I tested it with:
int main()
{
OFFFile fd;
FILE *fp = fopen("dat", "r");
ReadOFFFile(&fd, fp);
for (int i = 0; i < fd.nvert; ++i)
printf("%f %f %f\n", fd.vertices[i].x, fd.vertices[i].y, fd.vertices[i].z);
}
This is still an incomplete program: there are several more places where you need to check for errors (opening the file, calling malloc, etc.), and when you do detect an error, you need to at least print a useful error message before exiting or whatever.
One more thing. As I mentioned, those \n characters you had in the fscanf format strings were unnecessary and misleading. To illustrate what I mean, once you get the program working, have it try to read a data file like this:
OFF 2 0
0 0.8267261981964111
-1.8508968353271484 0.6781123280525208
0.7865174412727356 -1.8490413427352905 0.7289819121360779
Totally malformed, but the program reads it without complaint! This is one reason (one of several dozen reasons, actually) why the scanf family of functions is basically useless for most things. These functions claim to "scan formatted data", but their definition of "formatted" is quite loose, in that they actually read free-form input, generally without any regard for line boundaries.
For some advice on graduating beyond scanf and using better, more reliable methods for reading input, see this question. See also this section and this section in some online C programming course notes.
The line:
fscanf(srcFile, "%s\n");
is invoking undefined behavior. The compiler should warn you about that. Once you've invoked UB, there's no point in speculating further about what is happening.
It's not clear to me what you intended that line to do, but if you use %s in a scanf, you need to give it a valid place to write data. You should always check the value returned by scanf to ensure that you have actually read some data, and you should never use "%s" without a width modifier. Perhaps you want something like:
char buf[256];
if( fscanf(srcFile, "%255s ", buf) == 1 ){
/* Do something with the string in buf */
}
From your comment, it seems that you are intending to use that scanf to skip a line. I strongly recommend using a while(fgetc) loop instead of scanf to do that. If you do want to use scanf, you could try something like fscanf(srcFile, "%*s\n"), but beware that it will stop at the first whitespace, and not necessarily consume an entire line. You could also do fscanf(srcFile, "%*[^\n]%*c"); to consume the line, but you are really better off using a fgetc in a while loop.
Addressing title question:
"How do I read multiple floats from one line of a file"
...with suggestions for a non-scanf() approach.
Assuming the file is opened, (and a file pointer) fp is obtained ) , the first two lines are already handled, and values into ints, say the lines value is converted to int lines;
And given your struct definition (modified to use double to accommodate type compatibility in code below):
typedef struct
{
double x;
double y;
double z;
} Point3D;
In a function somewhere here is one way to parse the contents of each data line into the 3 respective struct values using fgets(), strtok() and strtod():
char delim[] = " \n";
char *tok = NULL;
char newLine[100] = {0};
Point3D *point = calloc(lines, sizeof(*point));
if(point)
{
int i = 0;
while(fgets(newLine, sizeof newLine, fp))
{
tok = strtok(newLine, delim);
if(tok)
{
if(parseDbl(tok, &point[i].x))
{
tok = strtok(NULL, delim);
if(tok)
{
if(parseDbl(tok, &point[i].y))
{
tok = strtok(NULL, delim);
if(tok)
{
if(!parseDbl(tok, &point[i].z))
{
;//handle error
}else ;//continue
}else ;//handle error
}else ;//handle error
}else ;//handle error
}else ;//handle error
}else ;//handle error
i++;//increment for next read
}//end of while
}else ;//handle error
Where parseDbl is defined as:
bool parseDbl(const char *str, double *val)
{
char *temp = NULL;
bool rc = true;
errno = 0;
*val = strtod(str, &temp);
if (temp == str)
rc = false;
return rc;
}

whole array printing instead of a single value within it

I need to be able to read in a word from a file one at a time and then be able to sort the text into a struct to track how many times words have been repeated however whenever I try to point to a specific word in the file I'm getting the whole file value instead of the specific word I'm trying to retrieve. apologies if the solution is simple, I still struggle with the different types of pointers and file commands
#include <stdlib.h>
#include <stdio.h>
char *inputFilename;
char *outputFilename;
char inputText[5000];
char outputText[5000];
int inputFlag;
int outputFlag;
int readfile(char **data){
FILE *input;
input = fopen(inputFilename, "rb");
int len = sizeof(*input);
printf("input length is %d\n", sizeof(char));
//First value is number of integers
int size;
fread(&size, sizeof(char), 0, input);
//allocate memory for that number of values
*data = (char*)malloc(sizeof(char) * size);
//Read in rest of data
fread(*data, sizeof(char), size, input);
//return size
printf("size is %d\n", size);
return size;
}
int valueSearch(char *vData, int argCount, char **argv){
for(int argLoop = 0; argLoop < argCount; argLoop++){
//If vData is detected then the next argument is the input file name
if(strcmp(argv[argLoop], vData) == 0){
return argLoop + 1;
}
}
return 0;
}
int main(int argc, char **argv){
char *data;
inputFlag = valueSearch("-i", argc, argv);
printf("input flag is %d\n", inputFlag);
inputFilename = argv[inputFlag];
if(inputFlag == 0){
printf("Please enter the text you would like to sort: ");
fgets(inputText, 5000, stdin);
}
else{
int size = readfile(&data);
}
int i = 0;
//Value that should be placed into struct
printf("readfile value 0:\n%s\n", data[i]);
free(data);
return 0;
}
Your last printf uses %s instead of %c. %s treats the argument as a pointer to a string (char array), and prints each byte until it encounters a byte of 0. You want %c, which prints out a single char.
Use %c instead of %s because %s is a identifier of string and that will print whole string.
printf("readfile value 0:\n%c\n", data[i]);
Hopefully it will work for you.
You need to become familiar with compiling with compiler warnings enabled, and then do not accept code until it compiles without warning or error.
You invoke Undefined Behavior on line 19 and again on line 76 by using improper conversion specifiers for int (instead of long int - line 19) and again as already noted on line 76 where you use %s instead of %c.
Further, you do not include string.h which is required for the use of strcmp on line 41. There is no declaration for strcmp so your compiler makes a guess at an implicit declaration.
You will also find unused variables len and size in your code. (lines 17 and 69, respectively).
All of these problems are readily apparent from your compiler output if you compile with -Wall -Wextra (and if wanted -pedantic) on gcc, with -Weverything on clang, or /Wall with VS (cl.exe) (you may want /W3 on VS as /Wall means all (and you will have to individually disable a handful of warning there with /wdXXXX where XXXX is the code for the warning you wish to disable).
While not an error, the standard coding style for C avoids the use of camelCase or MixedCase variable names in favor of all lower-case while reserving upper-case names for use with macros and constants. It is a matter of style -- so it is completely up to you, but failing to follow it can lead to the wrong first impression in some circles.
Moral of the story -- (1) enable compiler warnings, and (2) do not accept code until it compiles cleanly -- without warning. Letting your compiler help you write better code will eliminate a large percentage of the errors you spend time chasing :)

I have a set of values within a text file with delimiters how do I modify a value using C Programming

The text file that I have contains the below as the data (Starting with the K and D and P respectively as the ID)
K1234:Green_Book:A_green_book:10:
K3346:Red_Book:A_red_book:7:
D3333:Grey_Book:A_grey_book:15:
D1111:Black_Book:A_black_book:1:
P0000:White_Book:A_white_book:6:
what I would like to do is to modify the Data in the line that starts with D3333 and change the value from 15 to 17 in the text file. I really do not know how to do that as I am new to C programming and this has been troubling me for days now. I have tied searching all over the net but my searches were of no avail. If anyone can please help me with the code that can do that or something similar, I would really appreciate it. Thanks.
This is what i have done so far:
void show(){
FILE * fl;
long fl_size;
char * buffer;
size_t res;
fl = fopen("inventItems.txt", "r+");
if (fl == NULL) {
fprintf(stderr, "File error\n");
_getch();
exit(1);
}
fseek(fl, 0, SEEK_END);
fl_size = ftell(fl);
rewind(fl);
buffer = (char*)malloc(sizeof(char)*fl_size);
if (buffer == NULL) {
fputs("Memory error", stderr);
_getch();
exit(2);
}
res = fread(buffer, 1, fl_size, fl);
if (res != fl_size) {
fputs("Reading error", stderr);
_getch();
exit(3);
}
char * strtok_res;
strtok_res = strtok(buffer, ":");
while (strtok_res != NULL)
{
printf("%s\n", strtok_res);//this prints the values from the file to a new line when i test it
//however i DO NOT KNOW how to modify and save it back onto the text file
strtok_res = strtok(NULL, ":");
_getch();
}
_getch();
fclose(fl);
free(buffer);
}
Ok, looks like you are doing this along the right lines. A couple of comments:
Repitition and Readability
You are repeating yourself a lot; variations on
if(whateverPointer == NULL) {
fprintf("My error message", stderr);
_getch();
exit(2);
}
are used on three separate occasions; so should be moved into a separate function that takes a char* (string) message to pass into fprintf(), so that you can just do
if(whateverPointer == NULL) {
errorThenDeath("My message", 2);
}
This might seem irrelavent to the question, but as well as being a good habit to get into, it will make your code easier to read. The easier your code is to read, the easier it is for other people to help you when you are stuck: always remember this.
On a similar veign, you have indicated that you want to use a specific identifier to change a specific line. With this in mind, you should be breaking your problem up to identify this specific problem, so the code that performs that particular task should be contained in a function that might have a signature like this.
int changeLineValue(char** buffer, size_t size, char* identifier, int newValue)
{
...
}
Note the double pointer (char**) this is a pointer to the original char*. When inside this function, you can get at the original pointer by dereferencing it (*) so
*buffer = "hello";
Would change the buffer to the string 'hello'.
The Problem
I'm not sure tokenising the buffer is the right approach. strtok actually writes in \0 characters at the end of each token, which will make it a pain when you try to write this back to a file. Another thing to note; because you don't know how many digits your new number might have, you may have to resize the buffer to compensate. The function signature assumes this is done, and returns a number representing how many characters were added or removed (if any).
My general approach is to iterate through each character looking for newlines. Each time you've found one, check to see if it is the correct line, if it is, get the value, determine whether you need to resize the buffer and do so if necessary. Once that has been done, change the value.
int changeLineValue(char** buffer, size_t size, char* identifier, int newValue)
for(int i = 0; i < size; i++) {
if(buffer[i] == '\n' && isCorrectLine(*buffer, size, i)) {
int oldVal = getCurrentValue(*buffer, size, i);
int resize = getDigitsDifference(oldVal, value);
if(resize != 0) {
resizeBuffer(buffer, resize);
}
modifyValueInPosition(buffer, fl_size, i, value);
return resize;
}
}
}
Note; because of the way that the C language works, isCorrectLine(...) will only be called if buffer[i] == \n evaluates to true. This is called short circuit evaluation.
Obviously the code above calls a couple of functions that have not yet been created, so part of your task will be to implement them. Note that the second parameter which has been passed fl_size is defined as size_t not long even though fl_size is a long. You should change this variable to size_t.
Below I have provided the function signatures. Since you are trying to learn C, I am not going to implement them for you.
This function will be called each time a newwline is encountered, with 'index' set to the position of that newline. It should return a 0 if the identifier is not found at this position or a 1 if it HAS beenfound at this position. Do not change anything in the buffer at this point
int isCorrectLine(char* buffer, char* identifier, size_t size, int index) {
}
This function should iterate along the line and return the number before the next newline character ('\n')
int getCurrentValue(char* buffer, size_t fl_size, i) {
}
This number should return the difference between how many digits each number has. E.g. 1 = 1 digit, 324 = 3 digits so 3 - 1 = 2
int digitsDifference(int old, int new) {
}
This function takes a double pointer (char**) and reallocates the memory with a larger or smaller buffer to take into account the different number of digits if required.
void resizeBuffer(char** buffer, int resize) {
*buffer = // This is how you change the original pointer. Line is still incomplete
}
Now the buffer is the correct size, you can go ahead and change the value in the buffer. Again, this function is passed in the position of the newline before the correct line, so you need to iterate along the line, change the values in position. If you find yourself overwriting a newline character (because the new number is longer), you might need to move all of the characters after this value along
int modifyValueInPosition(char* buffer, size_t fl_size, int index) {
}
Writing to file
Once you have changed the original buffer, writing it back to file is fairly easy. Your main function should look something like this now
int main() {
// ...code that gets buffer from file. Note, make sure you close the file handle
// afterwards it isn't good practise to leave a file handle open
fl_size += changeLineValue(buffer, fl_size, identifier, newValue);
// ...Reopen file handle as you did before ...
fwrite(buffer, sizeof(char), fl_size, fileHandle);
// ...Close file handle...
}

Array Not Filling Properly

I am trying to deconstruct a document into its respective paragraphs, and input each paragraphs, as a string, into an array. However, each time a new value is added, it overwrites all previous values in the array. The last "paragraph" read (as denoted by newline) is the value of each non-null value of the array.
Here is the code:
char buffer[MAX_SIZE];
char **paragraphs = (char**)malloc(MAX_SIZE * sizeof(char*));
int pp = 0;
int i;
FILE *doc;
doc = fopen(argv[1], "r+");
assert(doc);
while((i = fgets(buffer, sizeof(buffer), doc) != NULL)) {
if(strncmp(buffer, "\n", sizeof(buffer))) {
paragraphs[pp++] = (char*)buffer;
}
}
printf("pp: %d\n", pp);
for(i = 0; i < MAX_SIZE && paragraphs[i] != NULL; i++) {
printf("paragraphs[%d]: %s", i, paragraphs[i]);
}
The output I receive is:
pp: 4
paragraphs[0]: paragraph four
paragraphs[1]: paragraph four
paragraphs[2]: paragraph four
paragraphs[3]: paragraph four
when the program is run as follows: ./prog.out doc.txt, where doc.txt is:
paragraph one
paragraph two
paragraph three
paragraph four
The behavior of the program is otherwise desired. The paragraph count works properly, ignoring the line that contains ONLY the newline character (line 4).
I assume the problem occurs in the while loop, however am unsure how to remedy the problem.
Your solution is pretty sound. Your Paragraph array is supposed to hold each paragraph, and since each paragraph element is just a small 4 bytes pointer you can afford to define a reasonable max number of them. However, since this max number is a constant, it is of little use to allocate the array dynamically.
The only meaningful use of dynamic allocation would be to read the whole text once to count the actual number of paragraphs, allocate the array accordingly and re-read the whole file a second time, but I doubt this is worth the effort.
The downside of using fixed-size paragraph array is that you must stop filling it once you reach the maximal number of elements.
You can then re-allocate a bigger array if you absolutely want to be able to process the whole Bible, but for an educational exercise I think it's reasonable to just stop recording paragraphs (thus producing a code that can store and count paragraphs up to a maximal number).
The real trouble with your code is, you don't store the paragraph contents anywhere. When you read the actual lines, it's always inside the same buffer, so each paragraph will point to the same string, which will contain the last paragraph read.
The solution is to make a unique copy of the buffer and have the current paragraph point to that.
C being already messy enough as it is, I suggest using the strdup() function, which duplicates a string (basically computing string length, allocating sufficient memory, copying the string into it and returning the new block of memory holding the new copy). You just need to remember to free this new copy once you're done using it (in your case at the end of your program).
This is not the most time-efficient solution, since each string will require a strlen and a malloc performed internally by strdump while you could have pre-allocated a big buffer for all paragraphs, but it is certainly simpler and probably more memory-efficient (only the minimal amount of memory will be allocated for each string, though each malloc consumes a few extra bytes for internal allocator housekeeping).
The bloody awkward fgets also stores the trailing \n at the end of the line, so you'll probably want to get rid of that.
Your last display loop would be simpler, more robust and more efficient if you simply used pp as a limit, instead of checking uninitialized paragraphs.
Lastly, you'd better define two different constants for max line size and max number of paragraphs. Using the same value for both makes little sense, unless you're processing perfectly square texts :).
#define MAX_LINE_SIZE 82 // max nr of characters in a line (including trailing \n and \0)
#define MAX_PARAGRAPHS 100 // max number of paragraphs in a file
void main (void)
{
char buffer[MAX_LINE_SIZE];
char * paragraphs[MAX_PARAGRAPHS];
int pp = 0;
int i;
FILE *doc;
doc = fopen(argv[1], "r+");
assert(doc != NULL);
while((fgets(buffer, sizeof(buffer), doc) != NULL)) {
if (pp != MAX_PARAGRAPHS // make sure we don't overflow our paragraphs array
&& strcmp(buffer, "\n")) {
// fgets awkwardly collects the ending \n, so get rid of it
if (buffer[strlen(buffer)-1] == '\n') buffer[strlen(buffer)-1] = '\0';
// current paragraph references a unique copy of the actual text
paragraphs[pp++] = strdup (buffer);
}
}
printf("pp: %d\n", pp);
for(i = 0; i != pp; i++) {
printf("paragraphs[%d]: %s", i, paragraphs[i]);
free(paragraphs[i]); // release memory allocated by strdup
}
}
What is the proper way to allocate the necessary memory? Is the malloc on line 2 not enough?
No, you need to allocate memory for the 2D array of strings you created. The following will not work as coded.
char **paragraphs = (char**)malloc(MAX_SIZE * sizeof(char*));
If you have: (for a simple explanation)
char **array = {0}; //array of C strings, before memory is allocation
Then you can create memory for it like this:
int main(void)
{
int numStrings = 10;// for example, change as necessary
int maxLen = MAX_SIZE; //for example, change as necessary
char **array {0};
array = allocMemory(array, numStrings, maxLen);
//use the array, then free it
freeMemory(array, numStrings);
return 0;
}
char ** allocMemory(char ** a, int numStrings, int maxStrLen)
{
int i;
a = calloc(sizeof(char*)*(numStrings+1), sizeof(char*));
for(i=0;i<numStrings; i++)
{
a[i] = calloc(sizeof(char)*maxStrLen + 1, sizeof(char));
}
return a;
}
void freeMemory(char ** a, int numStrings)
{
int i;
for(i=0;i<numStrings; i++)
if(a[i]) free(a[i]);
free(a);
}
Note: you can determine the number of lines in a file several ways, One way for example, by FILE *fp = fopen(filepath, "r");, then calling ret = fgets(lineBuf, lineLen, fp) in a loop until ret == EOF, keeping count of an index value for each loop. Then fclose(). (which you did not do either) This necessary step is not included in the code example above, but you can add it if that is the approach you want to use.
Once you have memory allocated, Change the following in your code:
paragraphs[pp++] = (char*)buffer;
To:
strcpy(paragraphs[pp++], buffer);//no need to cast buffer, it is already char *
Also, do not forget to call fclose() when you are finished with the open file.

Read from a .txt file and save it in an array.Trouble with fscanf

I want read from a .txt file which contains english sentences and store them into a character array. Each character by character. I tried but got segmentation fault:11 . I have trouble with fscanf and reading from a file in C.
#include<stdio.h>
#include<math.h>
#include<limits.h>
int main()
{
FILE* fp = fopen("file1.txt","r");
char c , A[INT_MAX];
int x;
while(1)
{
fscanf("fp,%c",&c);
if(c == EOF)
{break;}
A[x] = c;
x++;
}
int i;
for (i=0;i<x;i++)
printf("%c",A[i]);
return 0;
}
Problem 1: Putting the array onto the stack as A[INT_MAX] is bad practice; it allocates an unreasonable amount of space on the stack (and will crash on machines where INT_MAX is large relative to the size of memory). Get the file size, then malloc space for it.
fseek(fp, SEEK_END);
long size = ftell(fp);
rewind(fp);
char *A = malloc((size_t) size); // assumes size_t and long are the same size
if (A == NULL) {
// handle error
}
Problem 2: The fscanf is wrong. If you insist on using fscanf (which is not a good way to read an entire file; see problem 4), you should change:
fscanf("fp,%c",&c);`
should be
int count = fscanf(fp, "%c",&c);
if (count <= 0)
break;
Problem 3: Your x counter is not initialized. If you insist on using fscanf, you'd need to initialize it:
int x = 0;
Problem 4: The fscanf is the wrong way to read the entire file. Assuming you've figured out how large the file is (see problem 1), you should read the file with an fread, like this:
int bytes_read = fread(A, 1, size, fp);
if (bytes_read < size) {
// something went wrong
}
My initial answer, and a good general rule:
You need to check the return value, because your c value can never be EOF, because EOF is an int value that doesn't fit into a char. (You should always check return values, even when it seems like errors shouldn't happen, but I haven't consistently done that in the code above.)
From http://www.cplusplus.com/reference/cstdio/fscanf/ :
Return Value
On success, the function returns the number of items of the argument list successfully filled. This count can match the expected number of items or be less (even zero) due to a matching failure, a reading error, or the reach of the end-of-file.
If a reading error happens or the end-of-file is reached while reading, the proper indicator is set (feof or ferror). And, if either happens before any data could be successfully read, EOF is returned.
If an encoding error happens interpreting wide characters, the function sets errno to EILSEQ.
Hi you should declear till where the program should read data. You can access all characters even if you read line like a string.
try it out
#include<stdio.h>
#include<string.h>
#define INT_MAX 100
int main()
{
FILE* fp = fopen("file1.txt","r");
char c , A[INT_MAX];
int i;
int x;
j=0
while(fscanf(fp,"%s",A[j])!=EOF)
{
j++;
}
int i;
int q;
for(q=0;q<j;q++)
{
for (i=0;i<strlen(A[q]);i++)
printf("%c ",A[q][i]);
printf("\n");
}
return 0;
}

Resources