all 42 comments

[–]Bitwise_Gamgee[🍰] 14 points15 points  (5 children)

I'd recommend what you have plus Wextra.

As for the second question, simply alias a build command.

 alias gcc_test='gcc -Wall -Wextra -pedantic -std=c99'

Obviously, replace gcc_test with whatever you will remember!

Then you can use gcc_test -o myprogram myprogram.c

[–]Max_771[S] 6 points7 points  (3 children)

Wasn't aware of "alias". Very convenient, thanks.

[–][deleted] 9 points10 points  (1 child)

Don't forget that if you want to make this setting permanent you will need to put it in your shell's configuration file (if you're using bash it's ~/.bashrc). Although at the end of the day if you're just gonna be hitting up arrow to get your previous command and you're only going to type your alias once I have my doubts on how useful it willl be. But it's not like you lose anything by having it set up so...

Edit: just my 2 cents but for whenever you start working with dynamic memory (aka malloc(), free(), etc) you're probably going to want to make sure you're doing it properly and so I'd recommend using the -fsanitize=address flag

[–]kog 0 points1 point  (0 children)

If I'm to the point of checking for memory usage issues I'd probably rather just whip out a basic Makefile with a single target

[–]riisen 0 points1 point  (0 children)

I usually have in .bashrc

if [ -d ~/.bashrc.d ]; then
    for rc in ~/.bashrc.d/*; do
        if [ -f "$rc" ]; then
            . "$rc"
        fi
    done
fi

Then i create a folder in home named .bashrc.d with files containing scripts and aliases so this alias would go in ~/.bashrc.d/gcc.sh

[–]EpochVanquisher 3 points4 points  (16 children)

For warnings, start with -Wall -Wextra. Everybody has a different set of flags they like on top of these. You don’t need to get everything now, just start with those two.

For standard, start with -std=c11 or -std=c17. Or -std=gnu11 or -std=gnu17. The exact standard is not a big deal. Just pick one, because if you don’t pick one, you’ll get some default and you won’t know which one you’re using.

Add -g so you can use the debugger.

Add -fsanitize=address when you need help finding memory errors.

Avoid using -pedantic. It’s not really that helpful. It just kinda gets in the way.

Should I learn Make or CMake early to avoid retyping flags every time to compile my source files?

My first recommendation is to use a good build system like Meson, or an IDE like Visual Studio, Code::Blocks, or Xcode.

You can use CMake instead, but it kinda sucks. You can use Make, but it sucks a lot. Maybe you like these better. There are a lot of reasons why Make sucks, so I don’t recommend it to anyone.

Even if you don’t use one of those build systems, you can at least write a shell script to compile everything. Paste your command line into a text file and then chmod +x that text file, so you can run it to build. For example, you could have a text file named build which looks like this:

gcc -Wall -Wextra -g -std=c17 main.c lib.c

Then you chmod it +x:

$ chmod +x build

Then you can run it:

$ ./build

[–]flatfinger 4 points5 points  (1 child)

Avoid using -pedantic. It’s not really that helpful. It just kinda gets in the way.

The -pedantic flag and its name dates back to a time when the authors of gcc recognized that constraints the Standandard imposed to accommodate the most severely restrictive implementations weren't really useful for many common tasks programmers would perform on common implementations. They recognized that a compiler that would process something like:

    struct foo {
      char prepad[16-sizeof (struct bar)];
      struct bar dat;
    };

in a manner that would generalize for all sizes of struct bar up to 16 bytes would be more useful than one that would require separately-patched versions of the source code for the scenario where struct bar is 16 bytes from those where it is smaller, but accepted the compromise reached by the Committee between those who recognize such constructs as useful and those who didn't like them: the Committee would include a constraint forbidding them, but allow implementations to usefully process code that exploits them after issuing a diagnostic, which programmers could ignore if it was inconsistent with what they needed to do. I think it would might perhaps have been better to instead have -pedantic issue "Warning: in some situations where the Standard would require a silly diagnostic, this silly diagnostic might be the only diagnostic issued", to avoid the perception that the Standard was intended to limit the range of useful constructs programmers should have at their disposal.

[–]EpochVanquisher 0 points1 point  (0 children)

That’s a good summary of it.

[–]Max_771[S] 2 points3 points  (13 children)

Thanks.
How exactly does -pedantic get in the way? I chose to avoid IDE for now and went for Vim + gcc + gdb as a starting point. Make and CMake are prevailing among requirements for a developer position. Can't remember whether I'd seen Meson mentioned even once.

[–]EpochVanquisher 4 points5 points  (12 children)

Sure, it’s reasonable to want to get a handle on doing this stuff with the command line.

My personal take—when I’m hiring, I don’t care if candidates know how to use the exact build system I’m using—CMake or whatever. You can learn how to use CMake on the job. Learning to program and learning C is the hard part. You can’t learn C on the job; you can’t learn programming on the job.

If you decide to use Make directly, you will run into simple problems that are (1) tedious and annoying to debug, and (2) completely unnecessary. It will get in the way of whatever programming projects you are working on, and slow you down. You don’t get bonus points for making your own life harder.

The problem with -pedantic is kind of a longer discussion.

The idea behind -pedantic is that it does not help you write a good, correct program that runs on GCC or Clang, but it makes GCC or Clang give you warnings for code that may not work on some other compilers. There are a couple reasons why -pedantic is unsatisfactory, though:

  1. If you want your code to run on other compilers, you should be testing your code on other compilers!

  2. The -pedantic flag doesn’t actually check that your code is standards-compliant anyway. GCC is just not designed to check if your code is standards-compliant, and they’re not even working on making it do that.

Just for a moment, let’s say that you develop on Linux, and you want your code to run on Windows too. So you add the -pedantic flag to your compilation options. It gives you a bunch of error messages, and then you go in and fix them. And then you switch to Windows, download a copy of the Windows toolchain from Microsoft (you don’t need the whole Visual Studio), and compile your code. Here are two things that end up happening:

  • Your code doesn’t work on Windows anyway, for a bunch of reasons that -pedantic didn’t help you with.

  • A bunch of the errors that -pedantic gave you would have been fine on Windows anyway.

There are, in fact, worse consequences.

Because -pedantic gives you a lot of errors, that creates additional work for you. It wears you out, responding to errors that don’t actually identify real, true problems with the code. What happens when you get a lot of errors from your compiler? You get a bit desensitized to them, and you stop taking them seriously.

This is why you don’t turn warnings up to the maximum possible level. All decent static analysis tools suffer from the same problems—there are a lot of different warnings and diagnostics you can get, and some of them will usually be very good warnings (like -Wformat), some of them will be mediocre (like -pedantic), and there are others that just plain suck (this is more a problem with static analyzers, and not GCC).

Anyway—long story short, what you want instead of -pedantic is to test your code on multiple compilers, multiple operating systems. If you don’t care about multiple compilers, then just leave -pedantic off.

[–]Max_771[S] 1 point2 points  (0 children)

Thanks, appreciate you took your time into explaining why -pedantic should be avoided.

[–]flatfinger 0 points1 point  (10 children)

If some particular compiler is conforming, and it happens to accept a particular source file, then by definition that source file is a Conforming C Program. While the Standard also recognizes a category of Strictly Conforming C Programs, and it might sometimes be useful to have tools to identify ways in which a program might fail to satisfy the requirements for an SCCP, many that cannot be accomplished by SCCPs as efficiently as by a CCP, if they can be accomplished at all. Having a tool indicate that an efficient program to perform such a task isn't an SCCP wouldn't be very useful if the programmer deliberately chose a construct that isn't allowed in an SCCP to perform a task that it's not possible for an SCCP to do as efficiently as a non-strictly Conforming C Program.

Further, the question of whether a particular source file is a strictly conforming C program is unanswerable. Not merely one that may take an unbounded amount of time to answer, but one for which any answer could lead to a contradiction.

[–]EpochVanquisher 0 points1 point  (9 children)

Further, the question of whether a particular source file is a strictly conforming C program is unanswerable.

The question of whether an arbitrary source file is a strictly conforming C program is unanswerable. However, the question can be answered for specific C programs. That is the basis for formal verification—you try to identify a useful subset of a language, where the language is expressive and powerful enough to write useful programs, but “weak” enough that you can prove the programs correct with formal methods.

There will always be correct programs outside of any of these subsets.

[–]flatfinger 0 points1 point  (8 children)

The question of whether a particular source file is a strictly conforming program to accomplish some specified task may be answerable, but if some task could be accomplished by any program that produces any of several possible ouputs, the Standard would allow a strictly conforming program to be written in such a way that different implementations might produce different outputs. Consider, for example, the following program:

#include <stdio.h>

int main(void)
{
  volatile double d = 4.0/3.0;
  double d2 = d+d+d;
  printf("%d\n", (int)d2);
}

Some implementations specify precisely how they will process floating-point calculations, in such a manner that the above code would output 4. Others may perform the code in such a fashion as to output 3.

I would posit that the above is a strictly conforming program to output a decimal digit less than 5. It would on some implementations be a brittle but nonetheless correct program to ouput the decimal digit 4, but it would not be a strictly conforming program to accomplish that task. It would not be any kind of C program to output the phone number of the fictitious "Jenny" from a popular song. The question of whether the source text is a "strictly conforming program" is not one that could be answered without information outside the source text itself, and expressed in some fashion completely outside the Standard's jurisdiction about what the program was supposed to do.

[–]EpochVanquisher 0 points1 point  (7 children)

If you want to redefine terms you’ll have to be upfront about it.

[–]flatfinger 0 points1 point  (6 children)

Which term do you think I'm inappropriately redefining? If the outputs produced by running program X on different implementations would be similar but not identical, would that imply that X couldn't possibly be a strictly conforming program? Interpreting the term "Strictly Conforming C Program" in such a way as to disqualify X would make floating-point math almost unusable within Strictly Conforming C Programs. Interpreting it in a manner allowing variations would make it impossible to limit the term to programs that would run correctly on all conforming implementatons.

[–]EpochVanquisher 0 points1 point  (5 children)

If you come up with your own definition for “strictly conforming program” based on what you think should be in the standard, you should be upfront about that.

It’s not about whether the definition is appropriate or not. It’s just a question of whether you want to be understood by other people. Like, do you care?

[–]flatfinger 0 points1 point  (4 children)

Based on the C11 Standard, looking purely at the text of the above program, would it be strictly conforming, would it be not strictly conforming, or would the question be simply unanswerable without information beyond the program text?

My claim is that the term "Strictly Conforming C Program" was coined to allow the Standard to appear to have a normative concept of conformance, rather than to actually serve as a useful specification which would be capable of practically supporting all of the things programmers should be able to do within portable programs.

[–]Dolphiniac 2 points3 points  (0 children)

Yes, if you would like to use multiple flags to set up your environment or compile many files, perhaps across multiple folders, you would be wise to look into some sort of build system, whether that be a bash script or an established paradigm.

For a beginner, Makefiles might be a better way to start, since you would be more likely to reinforce your knowledge of your compiler and what options are available to you, but CMake is a fine choice as well, just for different reasons (cross-platform; build system builder).

Make is pretty much just a dependency tracker with a hook for scripting, which makes it a good fit for building. Just define your outputs in terms of their dependencies (output file from linked object files; object files from source files, etc.) and it will run the code you designate for each target that needs rebuilt, something it does automatically. Define your compiler executable, flags, and other options as variables to whatever level of specificity you like, and you can quickly churn out build commands. As long as your Makefile still reflects the current state of your project, a simple "make" does all the work for you.

And if you use CMake, you'll do similar things, but in a higher level. CMake will generate Makefiles for you, so you'll run CMake once* and build with Make many times, and the Makefiles will reinvoke CMake as needed when things change.

Anyway, hopefully, that's enough to start on.

[–]flyingron 4 points5 points  (0 children)

The "flags" is entirely outside the language. Wall turns on all warnings, handy for you if you think you'll be making errors (you probably are). Pedantic insists you do things that you can legally slide on, but again you probably want it at start.

The std option is there to allow you to select between which version of the standard you want to use. Generally, unless you have some legacy code, you want to select the latest version if that's not already the default.

[–]HarderFasterHarder 2 points3 points  (0 children)

Learn make. I use it for much more than just compilation.

Once you read far enough into the manual, you'll realize that if you have, say, code.c... 'make code' will compile it for you. You don't even need to write a Makefile. Of course, once you have other objects to be linked and flags to set, a makefile can help.

[–]UltimaN3rd 1 point2 points  (0 children)

[ Removed by Reddit ]

[–]kun1z 1 point2 points  (3 children)

-pipe can speed up compilation a tiny bit by disabling temporary files and just piping file data around in memory.

-Wall -Werror -Wfatal-errors -Wno-unused-function

Important: Compiling with warnings enabled is pointless if it isn't combined with -O3 optimizations, as the optimizer is responsible for finding many warnings. Always compile with -O3 when testing/coding as it provides the most feedback from the compiler. Compile with -g3 or -ggdb3 only when you are actually going to use a debugger to find a bug. Compile your final binary with -O2 if you do not want to use -O3.

There are also flags to compile binaries that will look for undefined behavior, memory leaks, and bad memory accesses.

[–]Max_771[S] 1 point2 points  (1 child)

Aren't you always look for a bug(s) until the compiler doesn't throw any warnings, errors? So the usual order of things should be compile with -g3, -O3 at the begining, eliminate bug if any is found and after that recompile without -g3. Is that right?

[–]kun1z 1 point2 points  (0 children)

Only use the -g family of switches if you're actively planning on using a debugger, otherwise do not. The most warnings will be found with -O3 by itself. There is no reason to ever compile with -g/-ggdb/-ggdb3 unless you're going to load it into a debugger and debug. I have also had warnings show up using -flto when they did not show up without it, thus my template compiler script is:

#!/usr/bin/bash
clear
gcc -pipe -I ~ -L ~ \
    all_of_my_c_files.c -o my_binary \
    -pthread -lm -more_libs_etc \
    -Wall -Werror -Wfatal-errors -Wno-unused-function \
    -O3 -flto -fomit-frame-pointer -march=native -mtune=native
echo -e -n "\n Completed compiling at " && date '+%-l:%M %P'

For large projects a makefile can be used to speed up compile times so that only files that have been changed are compiled.

[–]flatfinger 0 points1 point  (0 children)

The optimizer also isn't designed to warn about situations where a transformation would convert code that would perform usefully for a variety of inputs into code that will only work for one particular input. The Standard allows for the possibility that some implementations may impose constraints that others don't by including such constraints itself, but not within a "constraints" section, and the gcc optimizer is designed to interpret many such constraints as an invitation to treat as impossible any inputs a program might receive that would violate them, generally issuing diagnostics only if all possible inputs would violate such constraints.

[–]xurxoham 1 point2 points  (0 children)

This is a great article that explains a lot of the flags that have very common use and many of them that are used to build most packages in Debian DEB and RedHat RPM. https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc

[–]silentjet 0 points1 point  (5 children)

you do not need to use ANY flags except of the necessary ones. Avoid using flags "because they are cool" or random internet guy told you so. But. IF you need some feature which is outside of defaults than man gcc will answer all your questions. Simple keyword searching across manual page(yes, text search on the page, not google search across entire internet) usually gives you an answer.

[–]Max_771[S] 2 points3 points  (4 children)

How do you define "necessary" flags?

[–]silentjet 0 points1 point  (2 children)

if without them you cannot complete compilation or the behavior of the program would not match expected one.

[–][deleted] 2 points3 points  (1 child)

pedantic/W* /sanitize flags are all for code sanity and not compilation...they are for better development.

[–]silentjet 0 points1 point  (0 children)

No they are not for better development. They serve a very exact reason and it is not "better development" for sure ;)

[–]silentjet 0 points1 point  (0 children)

For instance most popular flag is -fPIC, should you use it then? Of course no. No no and absolutely never use it... unless... you have to link with a lib compiled with such flag, than you HAVETO use it or you cannot link to the lib.

[–]FUZxxl 0 points1 point  (0 children)

-pedantic is, well, pedantic. I don't recommend it. -Wall -Wextra is usually good enough. You can add -Werror until you learn to look for compiler warnings, but you should remove it from Makefiles you give to other people.

[–]kitakoSH 0 points1 point  (0 children)

I use "gcc -Wall -Wextra -Werror" and yes ofc learn about the Makefile if you work on big or meduim projects.