0

When I ran the APUE sample code shown below on my Mac, it throws a EXC_BAD_ACCESS exception. I have checked the file cursor, and it is in the right position 12. I even try to replace the fprintf with fputc. After that, it works fine and the exception is gone. But I want to know what happened out there and why.

#include "apue.h"

#define BSZ 48

int main(){
  FILE *fp;
  char buf[BSZ];

  memset(buf,'a',BSZ-2);
  buf[BSZ-2]='\0';
  buf[BSZ-1]='X';
  if ((fp = fmemopen(buf,BSZ,"w+")) == NULL)
      err_sys("fmemopen failed");
  printf("initial buffer contents: %s\n",buf);
  fprintf(fp, "hello, world");
  printf("before flush: %s\n", buf);
  fflush(fp);
  printf("after fflush: %s\n",buf);
  printf("len of string in buf = %ld\n",(long)strlen(buf));

  memset(buf,'b', BSZ-2);
  buf[BSZ-2]='\0';
  buf[BSZ-1]='X';
  fprintf(buf, "hello, world");
//  fputc('a',fp);
  fseek(fp,0,SEEK_SET);
  printf("after fseek: %s\n",buf);
  printf("len of string in buf = %ld\n", (long)strlen(buf));
}

Console out put as below:

/Users/heping/Documents/APUE-Example-Code/stdio/cmake-build-debug/memstr
initial buffer contents: 
before flush: hello, world
after fflush: hello, world
len of string in buf = 12
Exception: EXC_BAD_ACCESS (code=1, address=0x8)

Process finished with exit code 9

Last stack frame like this: it seems like that something is wrong with the file lock, I think.

stak frame

Neal.Marlin
  • 494
  • 5
  • 17
  • Hi, interesting, perhaps the size of BSZ? – IronMan Sep 18 '20 at 10:12
  • @IronMan I don't think so because the buffer is 48 bytes big and the file cursor is on 12 byte position. the String I'd like to write is 12 bytes long, so the total length after the write operation will not exceed the 48 bytes long. Right? And what's more, if I replace the fprintf with fputc, everything works fun. : ) – Neal.Marlin Sep 18 '20 at 10:19
  • Edit the question to provide a [mre]. – Eric Postpischil Sep 18 '20 at 10:24
  • @EricPostpischil Hi, Eric. This is the hole program, except the err_sys function is from the apue library. And the err_sys does not do much , but printing an error message and exit. You can just copy, paste and run with a tittle bit modification, including stdio.h , delete err_sys call. That's it. – Neal.Marlin Sep 18 '20 at 10:34
  • 3
    You accidentally passed `char buf[48];` as the `FILE*` parameter in your `fprintf` statement. That's why it crashes. My compiler stopped me from executing the program since I use `-Werror`. – MemReflect Sep 18 '20 at 10:46
  • Damn, kcuf me! Thanks @MemReflect, you save my ass! That's a stupid typo. And one more thing, as the book said the fprintf is buffered, before flushing, the buffer should not have been written. But as the console out put shows, the function does not work that way. Do you have any idea about this? – Neal.Marlin Sep 18 '20 at 10:53
  • 2
    @Neal.Marlin: (a) It is not the whole program except the `err_sys` function. It is missing a definition for `BSZ` and includes of several headers. (b) Since it is missing `err_sys`, it is not an [mre]. One reason for providing a **complete** reproducible example is so that other people can easily run the program for their own testing and debugging. It is not sufficient to allege what the missing parts are. When asking, you should either supply **everything** or rewrite the example not to need the parts you omit. Follow the guidelines and help people help you. – Eric Postpischil Sep 18 '20 at 11:04
  • @EricPostpischil You're right. – Neal.Marlin Sep 18 '20 at 11:06
  • @12431234123412341234123 There is a typo, a parameter wrong. But the CLion compiler doesn't seem to show a sign. – Neal.Marlin Sep 18 '20 at 11:10
  • Enable compiler warnings and you would not have this problem. In the case of gcc or clang use `-Wall`, `-Wextra`, `-Wpedantic` and `-Werror`, all of them. – 12431234123412341234123 Sep 18 '20 at 11:11
  • @MemReflect I use CLion, but it doesn't seem to show a sign. Pity. – Neal.Marlin Sep 18 '20 at 11:11
  • @12431234123412341234123 Thanks, I'll try to figure this out with CLion. – Neal.Marlin Sep 18 '20 at 11:13
  • @Neal.Marlin CLion is not a compiler, it is an IDE that hides the call to the compiler. CLion uses gcc or clang (Or MSVC on Windows). – 12431234123412341234123 Sep 18 '20 at 11:15
  • 1
    https://stackoverflow.com/questions/31790467/how-to-enable-all-compiler-warnings-in-clion – 12431234123412341234123 Sep 18 '20 at 11:17
  • @12431234123412341234123 Thank you for telling me this. I know that and I found this . https://stackoverflow.com/questions/31790467/how-to-enable-all-compiler-warnings-in-clion – Neal.Marlin Sep 18 '20 at 11:17
  • @Neal.Marlin The stream that `fprintf` uses is what does the buffering, not `fprintf` itself. In this case, the stream is a buffer already, and it apparently works as if you specified `setvbuf(fp, NULL, _IONBF, 0);`. Create a second buffer `char buf2[BSZ];` and set it as the buffer for `fp` using `setvbuf(fp, buf2, _IOLBF, BSZ);`. Then you might notice a difference. – MemReflect Sep 18 '20 at 11:19
  • @MemReflect I understand what you're talking about. the buffer condition is decided by the FILE, just like the stdout is line buffered, the stderr is unbuffered. Your code makes the buf2 FILE structure line buffered. But as the book said, fmemopen should return a FILE structure buffered. I think the book may be updated and I have a outdated copy. Thank you for explaining all those to me. – Neal.Marlin Sep 18 '20 at 11:27

1 Answers1

1

As already discussed in the comments, you passed the buffer buf to fprintf() and not the FILE pointer fp, which causes UB.

Every reasonable compiler will print a warning when you trying to do something like that you enabled the warning flags. You can avoid a lot of debugging when you enable all compiler warnings and only disable specific warnings when you are sure you do not want a warning for some type of error. As mentioned here How to enable all compiler warnings in CLion?, you can enable warnings by adding the compiler flags to CMakeLists.txt. For all warnings for C code, adding this line:

set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Werror")

-Wall will enable all normal warnings, -Wextra will enable additional warnings, -Wpedantic will warn about not strictly following the C standard you specified and -Werror will turn every warning into a error, so you have to fix the error before you can compile the program. In case you get warnings you do not want, you can disable them the same way, like this:

set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Werror -Wno-cast-function-type")

-Wno-cast-function-type will disable warnings about casting function pointers to different function pointers. You can read about all the warning options here