Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on What compiler options are recommended for beginners learning C?

Parent

What compiler options are recommended for beginners learning C?

+17
−0

When reading questions about C programming from beginners, I very often see them describing peculiar run-time errors and crashes, segmentation faults and similar. They have spent a lot of time chasing down the bug but failed.

Then upon viewing their code, I notice that the code should never have "compiled cleanly" - there were warnings, but the beginner didn't read them. If they had done so, it would have saved them a lot of time.

Not reading warnings could in turn be caused by the IDE used, which is hiding away warnings in some hard-to-spot window, or because they picked some "compile & run" option, or simply because they weren't paying attention.

Or possibly because they think that warnings mean "here's a little cosmetic issue that you should fix when you have time", and not "here is a severe bug that will likely prevent your program from working as expected" which is closer to the truth most of the time.

Unfortunately, a compiler isn't required to give an error upon C language violations. A "diagnostic message" is sufficient, as discussed at What must a C compiler do when it finds an error?

Are there any recommended compiler options beginners should use to avoid accidentally running programs with errors already spotted by the compiler?

Mostly interested in the "gcc-like" mainstream compilers: gcc, clang and icc, which have compatible command-line options.

History

0 comment threads

Post
+17
−0

My recommended beginner setup for gcc-like compilers is:

-std=c11 -pedantic-errors -Wall -Wextra -Werror

Here is an explanation of what these options do:

  • -std=c11. gcc & friends are by default set to include non-standard language extensions. These extensions are known as "GNU C" and extensively used in Linux programming in particular.

    However, when learning the language it is important to know what parts that are standard C and what parts that are non-portable compiler extensions. Beginners should focus on learning the C language as specified by the standard ISO 9899, before they move on to learn about various extensions and libraries.

    -std=c11 changes the compiler from using the default "GNU 11" to only use the features specified by the C language standard (ISO 9899:2011).

    There is a newer version of the language called "C17/C18", gcc and clang support it, but icc does not (yet). The differences between C17 and C11 are various detailed, advanced error fixes and nothing that concerns beginners. If you are using gcc or clang, you may as well use -std=c17 though.

  • -pedantic-errors. Together with the -std=c... option above, this forces the compiler into a strict mode. It doesn't mean "whine and be pedantic" as the name implies, but rather "give me diagnostic messages whenever I write invalid C". So this is the most "correct" mode to use when you want to see if your code is valid C or not.

    There's an option -pedantic that gives warnings for invalid C. -pedantic-errors is the same but gives errors and prevents the code from compiling.

  • -Wall doesn't mean "enable all warnings" as one might suspect. It rather means "give me some more warnings that are good to have".

  • -Wextra adds some more warnings still.

  • -Werror turns all warnings into errors and prevents the code from executing until the problems are fixed.


Some other options that may be helpful:

  • -O3 vs -O0. Enable or disable compiler optimizations. If you are concerned about program performance, then you need to use -O3 to enable all optimizations. This might be problematic when you are debugging/troubleshooting though, it's generally recommended to turn optimizations off when debugging. That is done with -O0.

  • -Wunused-result. This warns if you don't check the returned result from a function. Very handy to have but note that this one might get spammy, because most library functions that we commonly use (printf, scanf, strcpy etc) do return a result, which we aren't always interested in. You can cast the result of a function to (void) in case you aren't interested in it - that's good practice, but writing (void) in front of every single printf call might get tedious.

  • -ffreestanding. Always use this if you are compiling for an embedded system, such as a microcontroller application.

  • -fno-strict-aliasing is also strongly recommended for embedded systems, but that's a more advanced topic that I won't address here since this answer is aimed to beginners. (This option might only have an effect on gcc, I believe clang ignores it.)

Do not use:

  • -ansi. There's a common misunderstanding that this enables strict C compilation. It does not, that's -std=c11 -pedantic-errors as explained above. -ansi enables "ANSI C", which is the nickname of the old, obsolete C90 standard, which should be avoided since it comes with a lot of language flaws that have been corrected over the years.
History

4 comment threads

scanf (9 comments)
Reason for -ansi misunderstanding (1 comment)
Is -Wunused-result already enabled by default? (1 comment)
-ansi (3 comments)
scanf
alx‭ wrote 2 months ago · edited 2 months ago

I never understood why scanf(3) is taught to new programmers.

fgets(3) is way simpler, and easier to use. And scanf(3) is in general avoided outside of teaching, so it would be better to avoid it entirely. fgets(3) also helps programmers think of text files, which is usually a good format.

Here's what the manual page says about it:

     The scanf() family of functions scans  formatted  input  like  ss‐
     canf(3),  but read from a FILE.  It is very difficult to use these
     functions correctly, and it is preferable  to  read  entire  lines
     with fgets(3) or getline(3) and parse them later with sscanf(3) or
     more specialized functions such as strtol(3).
Lundin‭ wrote 2 months ago · edited about 1 month ago

alx‭ The whole of stdio.h is just unimportant to learn since the whole lib is far too poor to be used in professional programs, always was. fgets for example has a bad API that never got fixed, you can never know how much it read. People need to stop recommending fgets - the proper recommendation is to stay clear of the entire stdio.h.

alx‭ wrote 2 months ago · edited 2 months ago

Why would you want to know how much it read? If you really want to know, it should be as simple as strlen(buf), which yeah, might be a bit inefficient, but compared to reading a file, is that observable? Or does it have any obscure/dangerous/tricky behavior?

What do/would you use instead?

Lundin‭ wrote about 1 month ago

alx‭x When you are reading from all manner of streams and files - not just stdin - then the nature of the input can be garbage or incomplete, or you hit end of buffer/file. When that happens, you would like to know how much it was able to read before hitting the end, since that isn't necessarily an "error" scenario but normal use, working with variable buffers/files. When designing an API that reads from a stream/buffer/file it is standard practice to use two different parameters: one telling the function how large the pre-allocated output buffer is and another that the function can use to fill in how many bytes it actually wrote. As reference, a somewhat well-designed API is the semi-modern (1990s) Windows API where you'd have parameters nNumberOfBytesToRead and lpNumberOfBytesRead. A whole lot better than stdio.h or read/write etc: old crap functions with broken API from the 1970-1980s.

alx‭ wrote about 1 month ago

Lundin‭

If I understand correctly, you mean when reading files that are not text files. Of course, fgets(3) is not good for reading non-text files. But that's not because fgets(3) is a bad API, but because it's not suitable for non-text files. There are APIs for non-text files.

For text files, fgets(3) is a good API (IMO).

For non-text: Doesn't fread(3) do that? It asks for the buffer size (it actually asks for the element size --often one--, and the number of elements in the buffer). It then returns the number of elements read.

Actually, it has some issues with the return value, since it returns a number of elements, but that might not exactly match the number of bytes read, so you probably want to call it with an element size of 1 and then pass n*size as the number of elements. Any other issues with it? I'm honestly not too familiar with fread(3), since most of the files I use are proper text files, and imperfect files are rejected as errors.

Lundin‭ wrote about 1 month ago

alx‭ No, when reading streams. They are usually text, but not necessarily sanitized. It could be a file, command prompt input or incoming data from a serial port. Quite likely unknown amounts. What am I to do when I want to verify a chunk of text of an unknown amount? Well, first of all I probably got to iterate over it yet again to determine the size, because fgets was too lazy to tell me as much, even though it knows the size read internally. Really bad API, period.

alx‭ wrote 30 days ago · edited 29 days ago

POSIX defines text files (that includes streams) as being composed of 0 or more lines. And lines are defined to not contain null bytes and be no longer than LINE_MAX characters (including the '\n', but not the '\0' of the string in which it is stored).

If a stream contains null bytes, or doesn't respect the length, then it's not a valid text stream. In any of those two cases, the validation should be performed by the program. If the buffer passed to fgets(3) was of LINE_MAX+1 elements, then validating the lines is relatively simple:

I wrote a function for doing that:

char *
stpsep(char *s, const char *delim)
{
	strsep(&s, delim);
	return s;
}

Which I use as:

while (fgets(buf, countof(buf), stream) != NULL) {
	if (stpsep(buf, "\n") == NULL)
		errx(EXIT_FAILURE, "Non-text file");
	...
}

After that stpsep() check, the input line is validated. Should fgets(3) validate the line? Maybe. Would it result in simpler code? Not much.

alx‭ wrote 30 days ago · edited 30 days ago

Lundin‭

And I forgot to mention the obvious: POSIX also requires that all lines are terminated by '\n'. If a line doesn't contain that '\n', it's invalid.

And also forgot to mention: stpsep() does two things: it validates the line, and it removes the '\n'. Both at the same time.

alx‭ wrote 29 days ago · edited 29 days ago

Lundin‭

I've been thinking about how an ideal fgets(3)-like API would look like, and I've developed an API that I think I like slightly more than fgets(3).

// fgettextline - FILE get text line
char *
fgettextline(char *buf, size_t n, FILE *stream)
{
	if (n > INT_MAX) {
		errno = EOVERFLOW;
		return NULL;
	}
	if (fgets(buf, n, stream) == NULL)
		return NULL;  // fgets(3) sets errno on error (in POSIX).
	if (stpsep(buf, "\n") == NULL) {
		errno = EILSEQ;
		return NULL;
	}
	return buf;
}

This allows moving the error handling out of the loop.

errno = 0;
while (fgettextline(buf, countof(buf), stream) != NULL) {
    // validated string here, with the '\n' stripped.
    ...
}
if (errno != 0)
    err(EXIT_FAILURE, "fgettextline");