2

I have a FILE* file that holds some binary data. Let's say that this data are a list of double and that the last entry is a string that describes what are those double. I want to modify this string (the new string might be shorter). So first i delete the old string. I need to find the starting point of the string :

fseek(file,-size(sring.size()),SEEK_END);

and then what should i do ? i found Delete End of File link but i don't know which one to use... Once the file is re-sized, can i simply write my new string using fwrite ?

Community
  • 1
  • 1
PinkFloyd
  • 2,103
  • 4
  • 28
  • 47

2 Answers2

6

Neither FILE* nor iostream support truncation. If you want to edit a file so that the new file is shorter than the old, you have two solutions:

  • The usual solution is to copy the original file into a new file, making any changes as you go. When finished, close the new file, verify that there are no errors (an important point), then delete the original file and rename to new file to have the original name. This may cause problems on Unix systems if there were hard links to the original file. (Typically, this isn't an issue, since everyone uses soft links now. If it is, you should stat the original, and if the st_nlink field is greater than 1, copy the new file onto the original, and then delete the new file.) On the other hand, it is the most generic option; it works for all types of modifications, anywhere in the file.

  • There are usually system specific functions at the lower level to truncate a file. Under Unix, this is ftruncate. But you'll need to find the byte count where you want to truncate first; ftruncate requires an open file, but it won't truncate at the current position in the file. So you'll have to 1) find the start of this last line in the file, 2) seek to it, 3) write the new value, 4) call ftell (or ftello, if the length can be too large to fit on a long) to find the new end position. At this point, you have the problem of synchronizing your FILE* with the lower level; personally, I'd fclose the file, then reopen it with open, and do the ftruncate on the file descripter from this open. (In fact, personally, I'd do the entire job using open, read, lseek, write, ftruncate and close. And maybe stat to find out the file length up front. If you don't have to translate the doubles, there's really nothing that FILE* adds.

As a general rule, I'd go with the first solution, and only try the second if it turns out to be too slow. (If the file contains a couple of billion doubles, for example, copying them will take some time.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • great answer, but it rises a few questions for the second solution that you propose. you mean that i need to fopen first, then find where i want to truncate, then fclose, the open why ? why couldn't i leave the file open? and what do you mean by "If you don't have to translate the doubles, there's really nothing that FILE* adds." – PinkFloyd Mar 01 '13 at 12:47
  • is it related to the comment that you give to @aaaaaa123456789 ? – PinkFloyd Mar 01 '13 at 12:48
  • @user2110463 Second question first: what `` (and iostream) really add to your basic system level IO is 1) portability 2) parsing of various types of input (`double`, `int`, etc.). If you don't need the latter, and you're using `ftruncate`, so you don't have the former, you might as well do everything in terms of basic system level IO. – James Kanze Mar 01 '13 at 12:53
  • @user2110463 As to the first question, it may be just over-precautious, but the standards don't really specify how `` works in conjunction with system level IO. I would expect `fseek`-`fputs`-`ftruncate`-`fclose` to work, but I've been surprised before, and I don't know of any guarantee. – James Kanze Mar 01 '13 at 12:54
  • @user2110463 And yes, it is precisely the same issues as in my response to @aaaaaa123456789. I think just slipping the `ftruncate` between an `fflush` and the close _should_ work, but I've seen so many things that I think should work, but didn't, that in the absence of an explicit guarantee... – James Kanze Mar 01 '13 at 12:56
5

If you want to resize a file, then ftruncate() (http://www.linuxmanpages.com/man2/ftruncate.2.php) is the function you're looking for. You'll need to call fileno() on the FILE * structure to get the file descriptor for ftruncate(), though.

As for appending the new data (the new string) once the file has been reduced in size, just seeking to the end (fseek(file, 0, SEEK_END)) and fwrite()'ing there should do it.

EDIT: remember to call fflush() before truncating the file!

aaaaaa123456789
  • 5,541
  • 1
  • 20
  • 33
  • great thanks ! but do i really need to seek for the end after ftruncate() ? it should already at the right position !? – PinkFloyd Mar 01 '13 at 09:27
  • 1
    No question is stupid, if it truly comes from lack of knowledge. Everybody knew nothing once. As for why (in case somebody else wonders the same, or in case you're unclear), it's because the file pointer still points to wherever it was pointing -- not necessarily to the new end of the file. Even if it does, it's a good programming practice to `fseek()` anyway, just in case. – aaaaaa123456789 Mar 01 '13 at 09:35
  • @aaaaaa123456789 Good practice is relative. Doing operations using the low level funcdtions (like `ftruncate`) and `FILE*` operations at the same time is fraught with dangers, and is bad practice to begin with. The `fflush()` should help, but I wouldn't try any operations on the file after the `ftrunctate` (and hope that an `fclose()` won't do anything but `close()`, because of the previous `fflush()`). – James Kanze Mar 01 '13 at 09:47
  • @JamesKanze You could close the file, truncate it with `truncate()`, and reopen it. It seems like an unnecessary burden to me, since the file is supposed to have no buffered data after `fflush()`. But if you want to be extra careful, then that's the way to go. Another way would be to `fflush()` the file (ensuring no writes are pending) and just use `truncate()` ignoring completely that the file is open. That supposedly works (if `fflush()` does what it says it does, it should), although I don't know how compliant the libraries and such are with stuff like that. – aaaaaa123456789 Mar 01 '13 at 09:51
  • @aaaaaa123456789 For any reasonable implementation of ``, just flushing before calling `ftruncate()` should be sufficient. But over the last 30 years, I've seen a lot of unreasonable things, so I've become ultra-cautious. (IMHO, unless he wants to parse the doubles, doing the whole thing using the low level routines wouldn't be much more effort, and would be surer.) – James Kanze Mar 01 '13 at 11:19
  • but ftruncate is only for Unix systems. not good solution for Windows users – vincent thorpe May 05 '19 at 10:23
  • @vincentthorpe Windows has a `SetEndOfFile` function you can use to truncate to the current position. You have to seek to that position beforehand, though. – aaaaaa123456789 May 06 '19 at 04:14