Search code examples
gccdependencieslinker-errors

How to combine gcc flags -o and -MM correctly?


During compilation and linking of a simple program, I encountered a weird behavior of gcc that I cannot explain.

This here works perfectly fine for me

cc -Wall -c -o obj/main.o src/main.c
cc -Wall -c -o obj/foo.o src/foo.c
cc -Wall -o bin/program obj/main.o obj/foo.o

However, once I ask gcc to create and store dependency information in a separate file, the linker throws an error:

cc -MM -MP -MF deps/main.d -Wall -c -o obj/main.o src/main.c
cc -MM -MP -MF deps/foo.d -Wall -c -o obj/foo.o src/foo.c
cc -Wall -o bin/program obj/main.o obj/foo.o
# obj/main.o: file not recognized: File truncated
# collect2: error: ld returned 1 exit status

I expect gcc to write the dependency information in the file provided with the -MF statement. In seems that the object files are somehow modified such that the linker cannot read them anymore.

Any suggestions?


Solution

  • Your object files are truncated to 0 bytes as a side-effect of the preprocessor option -MM

    Refer to the documentation of GCC's preprocessor options and you see that -MM has the same effect as -M, except that dependency rules are not generated with respect to any system headers #include-ed by the translation unit.

    For the -M option, you will see:

    Instead of outputting the result of preprocessing, output a rule suitable for make describing the dependencies of the main source file...

    And also:

    Passing -M to the driver implies -E...

    And turning to option -E you will see:

    If you use the -E option, nothing is done except preprocessing.

    (That is actually a bit sloppy. Really it should read: "If you use the -E option, no translation is done except preprocessing.")

    The -o filename option specifies that whatever kind of output file is requested - preprocessed code, assembly, object code or binary - it shall be written to filename.

    So, for example, the command

    cc -MM -MP -MF deps/main.d -Wall -c -o obj/main.o src/main.c
    

    instructs the compiler (among other things) to:-

    • Perform no translation beyond preprocessing (by implied -E).
    • Don't output the result of preprocessing; output a dependency rule instead (by -MM)
    • Don't write the dependency rule to the output file (obj/main.o); write to it deps/main.d instead (by -MF deps/main.d)

    As a result, the output file obj/main.o is clobbered and opened to receive the output of preprocessing; there is no output of preprocessing; the generated dependency rule is written to deps/main.d, and when the command completes obj/main.o is closed containing 0 bytes. The same thing happens to obj/foo.o.

    This is not what you want to happen, of course. The options -M and -MM are useful only for generating dependency files independently of compilation. But you want a command to generate a dependency file deps/<name>.d and to proceed with translation all the way to object code, output in obj/<name>.o

    To do what you want, replace the option -MM with -MMD in your commands. In the documentation you'll see:

    -MD

    -MD is equivalent to -M -MF file, except that -E is not implied... Since -E is not implied, -MD can be used to generate a dependency output file as a side effect of the compilation process.

    -MMD

    Like -MD except mention only user header files, not system header files.

    [my emphasis]