As a school assignment I'm tasked with writing a program that opens any text file and performs a number of operations on the text. The text must be loaded using a linked list, meaning an array of structs containing the char pointer and the pointer to the next struct. One line per struct.
But I'm having problems actually loading the file. It seems the memory required to load the text into memory must be allocated before I actually read the text. Hence I have to open the file several times. Once to count the number of lines, then twice per line; once to count the characters in the line then once to read them. It seems absurd to open a file hundreds of times just to read it into memory.
Obviously there are better ways of doing this, I just don't know them :-)
Examples
Can the point from which fgetc fetches a character be moved without re-opening the file?
Can the number of lines or characters in a file be checked before it is "opened"?
Can I somehow read a line or string from a file and save it to memory without allocating a fixed amount of bytes?
etc
There is no need to open the file more than once, nor to pass through it more than once.
Look at the POSIX getline() function. It reads lines into allocated space. You can use it to read the lines, and then copy the results for your linked list.
There is no need with a linked list to know how many lines there are ahead of time; that's an advantage of lists.
So, the code can be done with a single pass. Even if you can't use getline(), you can use fgets() and monitor whether it reads to end of line each time, and if it doesn't you can allocate (and reallocate) space to hold the line as needed (malloc(), realloc() and eventually free() from <stdlib.h>).
Your specific questions are largely immaterial if you adopt anything of the approach I suggest, but:
Using fseek() (and in extremis rewind()) will move the read pointer (for fgetc() and all other functions), unless the 'file' does not support seeking (eg, a pipe provided as standard input).
Characters can be determined with stat() or fstat() or variants. Lines cannot be determined except by reading the file.
Since the file could be from zero bytes to gigabytes in size, there isn't a sensible way of doing fixed size allocations. You are pretty much forced into dynamic memory allocation with malloc() et al. (Behind the scenes, getline() uses malloc() and realloc().)
You cannot count the number of lines in a file without actually traversing it. You could get the total file size, but that's not whats intended here. The idea of using a linked list of lines is that you operate on the file one line at a time. You do not need to read anything in advance. While you haven't read the whole file, read a line, add it to its own node at the end of the linked list, move to the next line.
Regarding your first question: you can change the position in the file you are reading from with the fseek() function.
There are several ways you could do this. For example, you could have a fixed-size buffer, fill it with bytes from the file, copy lines from the buffer to the list, fill the buffer again and so on.
Related
How can I read in C a line from the console to not initialize a some array like int buf[30];? I need to allocate it once and required length, i.e. to I can know input characters count before read it...
Is it possible in C?
There is no way to know the number of characters available in standard input before reading them. You can, however, use getline(3), which will read until \n and then return the data in a dynamically-allocated buffer holding the data (along with the size of that buffer). You must free the buffer when you're done with it.
You should be aware that this will routine will block until it reads a newline. It's also difficult to use this routine safely, as malformed inputs are not handled well. (What if the input has no newline?) This is one of the reasons many applications often read a fixed length input.
I suspect that what you are requesting is about dynamic memory.
With dynamic memory we can create arrays with dynamic capacity, so the number of slots inside can be varied on run-time. That way, you don't need to decide at coding the size of a particular array.
To generate that kind of dynamic array you will need to create a pointer referring to a space in memory.
int *array;
Once we have that connection between memory and a variable, we now need to set how much memory do we want(how many slots inside the array).
array = (int *)malloc(sizeof(int) * numberOfSlots);
This function malloc its provided by an external library called stdlib.h.
It will request the computer for a space in the memory. That space is defined inside those brackets (). There you set the number of bytes you want to request.
If what we want is an array of integers, we multiply the size of an integer with the slots we need.
To access or modify data inside an array, you can keep it simple by using [], like this:
array[0] = 1;
Important note: Never access or modify data inside an array without requesting memory before!
To read the numbers of chars in a line, you can simple use a loop, and read letter by letter until you find that '/n' character.
Why, in C, do you need a separate buffer to read a FILE *? When you declare a FILE * and assign to it with fopen, does the file then not exist in contiguous memory starting at the address of said pointer? I'm struggling to make the connection as to why you need need to read via fread() into a separate buffer. If someone could explain how theFIlE *file = fopen(filename, "r") and the subsequent fread(&buffer,...) work in conjunction it would help my understanding tremendously. Thanks in advance.
The FILE * returned by fopen is an unnecessary, but useful, layer of indirection.
Theoretically, fopen could have been designed to read the whole file into a buffer in memory, and just return you that buffer.
The issue approach is that it's not flexible at all. It forces you to read the entire file for all file IO operations, which is very undesirable. For example, here are some problems that would come about:
How could you read a file that's too big to fit in RAM?
What if you just want to append a new line at the end of a file (such as for logging). You would have to read the whole file, append the line at the end, then rewrite the entire file back. Expensive!
What if you're only interested in reading a small part of a file, such as reading the magic number to identify the file's type, without regard for its actual content?
What if you wanted to simultaneously edit the file from multiple programs. Each program need to constantly reread the whole file into memory, to ensure it kept up-to-date.
fopen returns a file handle that identifies a file still on disk. How much you read out of this file into memory is entirely up to you.
The explanation given above is pretty much self explanatory , still I would try to make it simple (in case anyone has problem understanding it)
In Short,consider this example and you yourself would know 'why?'
1) your files might be too large and stored in your hard drive , then if you try reading it frequently, don't you think this is an overhead for loading whole file again and again.
2) And more worse say the file is huge then if you load whole of your file it consumes your RAM even if you don't need whole file at once.
Why, in C, do you need a separate buffer to read a FILE *?
First thing, Because reading into buffers and then using it is always faster.
does the file then not exist in contiguous memory starting at the
address of said pointer?
May or May not be ,depending on its size.
If someone could explain how theFIlE *file = fopen(filename, "r") and
the subsequent fread(&buffer,...) work in conjunction
The fopen() function is used to open a file and associates an I/O stream with it. This function takes two arguments. The first argument is a pointer to a string containing name of the file to be opened while the second argument is the mode in which the file is to be opened.
Various modes can be like r, r+, w, w+, a, a+ .
The fopen() function returns a FILE stream pointer on success while it returns NULL in case of a failure.
Look here for detailed info.
Why, in C, do you need a separate buffer to read a FILE *?
No buffer aren't necessary while they are usually present to accelerate I/Os.
When you declare a FILE * and assign to it with fopen, does the file
then not exist in contiguous memory starting at the address of said
pointer?
Certainly not, 'cause this would be at least inefficient (why read a entire file huge if it is not needed at the end?) and at worst impossible at all (RAM size is usually much less than DISK size).
If someone could explain how the FILE *file = fopen(filename, "r") and
the subsequent fread(&buffer,...) work in conjunction it would help my
understanding tremendously.
Then FILE * is not an handle to a memory object that contains the file data, but is a memory object that contains data to help accessing file data on disk. That opaque object (opaque means don't try to look inside details are hidden) contains for example the current offset (remember when you read or write this is done at a given offset and this would modify the offset accordingly), or the open mode (this way writing into an opened for read file will correctly fails), or some buffer (that may contains part of the file and sometimes the whole file!), etc. A FILE * is handle as a handle for a door. Don't confuse file and FILE*, the first is a generic term to embrace what you already know (data on disk), then second is a type to represent an opened file which is a dynamic object to represent manipulation of a given file.
I'm struggling to make the connection as to why you need need to read
via fread() into a separate buffer.
If you don't have/can't have the file in memory, then you need to ask for reading the part you are interested in.
Prepending to a large file is difficult, since it requires pushing all
other characters forward. However, could it be done by manipulating
the inode as follows?:
Allocate a new block on disk and fill with your prepend data.
Tweak the inode to tell it your new block is now the first
block, and to bump the former first block to the second block
position, former second block to the third position, and so on.
I realize this still requires bumping blocks forward, but it should be
more efficient than having to use a temp file.
I also realize the new first block will be a "short" block (not all the data in the block is part of the file), since your prepend data is unlikely to be exactly the same size as a block.
Or, if inode blocks are simply linked, it would require very little
work to do the above.
NOTE: my last experience directly manipulating disk data was with a
Commodore 1541, so my knowledge may be a bit out of date...
Modern-day operating systems should not allow a user to do that, as inode data structures are specific to the underlying file system.
If your file system/operating system supports it, you could make your file a sparse file by prepending empty data at the beginning, and then writing to the sparse blocks. In theory, this should give you what you want.
YMMV, I'm just throwing around ideas. ;)
This could work! Yes, userland programs should not be mucking around with inodes. Yes, it necessarily depends on whatever scheme used to track blocks by whatever file systems implement this function. None of this is a reason to reject this proposal out of hand.
Here is how it could work.
For the sake of illustration, suppose we have an inode that tracks blocks by an array of direct pointers to data blocks. Further suppose that the inode carries a starting-offset and and ending-offset that apply to the first and last blocks respectively, so you can have less-than-full blocks both at the beginning and end of a file.
Now, suppose you want to prepend data. It would go something like this.
IF (new data will fit into unused space in first data block)
write the new data to the beginning of the first data block
update the starting-offset
return success indication to caller
try to allocate a new data block
IF (block allocation failed)
return failure indication to caller
shift all existing data block pointers down by one
write the ID of the newly-allocated data block into the first slot of the array
write as much data as will fit into the second block (the old first block)
write the rest of data into the newly-allocated data block, shifted to the end
starting-offset := (data block size - length of data in first block)
return success indication to caller
I need two file pointers (FILE *) to operate alongside each other. One is to apply append operations and another is for reading and overwriting.
I need appends to the file from one pointer to be recognised by the other file pointer so that the other file pointer can both correctly read and overwrite this appended data.
To synchronise the data, it appears that using fflush() on the appending file pointer works (at least for reading it does), but is this the correct way to achieve what I want and is it portable?
Thank you.
You should be able to do that with one pointer (and thus not having to sync unnecessarily). Just use fseek(f, SEEK_END, 0); when you want to add at the end. Use "rb+" to make the file readable and writeable.
As long as you don't use multiple threads to access the file, this should work just fine.
There's no way in any operating system I'm aware of for a program to prepend data to a file efficiently.
And yet, this doesn't seem difficult -- the file system can add another extent to a file descriptor if needed.
So the question is, why don't operating systems implement this (rather trivial) operation?
I don't think it's as easy as you suggest. It's true that the file-system could allocate a new block, store the prepended data in it, change the file pointer to point to that block and then chain the rest of the file from that block. Just like adding a node to the front of a linked list, right?
But what happens when (as is probably the case) the prepended data doesn't fill the assigned block. I don't imagine that many filesystems would have a machanism for chaining partial blocks, but even if they do it would result in huge inefficiencies. You'd end up with a file consisting of mostly empty blocks, and you have to have to read and write the entire file to defragment it. Might as well just do the read-and-write operation up front when you're prepending in the first place.
In prepending or appending data to a file, there is always an issue of allocating space. Appending additional space to a file is much easier than prepending because of file descriptors pointing to the beginning of a file's stream. If you want to append to a file, the file descriptor need not be changed, just the size of the file and the allocated memory. If you want to prepend to a file, a new file descriptor must be immediately instantiated, either to write the prepended data to first or to store the location of the data being prepended while the original is updated.
Making a new file descriptor can be tricky, as you must also update any references pointing to it. This is why it is easy for an OS to implement appending data and slightly harder to implement prepending.