Commit 4e2d653f authored by Martin Dørum's avatar Martin Dørum

first draft of two sections

parent 86461e60
......@@ -5,9 +5,11 @@ Git: <
I [wrote a blog post]( about some
weird features of C, the C preprocessor, and GNU extensions to C that I used in
my testing library. This post will be about some of the weird compiler bugs,
limitations, and annoyances I've come across. This post is not meant to bash
compilers; most of these annoyances have good technical or practical reasons.
my testing library.
This post will be about some of the weird compiler and language
quirks, limitations, and annoyances I've come across.
This post is not meant to bash compilers or the specification;
most of these quirks have good technical or practical reasons.
## Compilers lie about what version of the standard they support
......@@ -105,7 +107,7 @@ so even though our version check is completely irrelevant in the Intel compiler
at least it will only prevent an otherwise C11-compliant Intel compiler
from using `_Generic` if the user has an older version of GCC installed.
## \_Pragma in macros
## \_Pragma in macro arguments
C has had pragma directives for a long time;
it's a useful way to tell our compiler something implementation-specific;
......@@ -185,6 +187,128 @@ fprintf(stderr, "TRACE: %s\n", "abort_if_zero(my_float)");
#pragma GCC diagnostic pop
## Some warnings can't be disabled
## Line numbers in macro arguments
Until now, the quirks I've shown have been issues you could potentially
encounter in decent, real-world code.
If this quirk has caused issues for you however,
it might be a sign that you're slightly over-using macros.
All testing code in Snow happens within macro arguments.
This allows for what I think is a really nice looking API,
[and allows all testing code to be disabled just by changing one macro definition](
This is a small example of a Snow test suite:
``` C
#include <stdio.h>
#include <snow/snow.h>
describe(files, {
it("writes to files", {
FILE *f = fopen("testfile", "w");
assertneq(f, NULL);
char str[] = "hello there";
asserteq(fwrite(str, 1, sizeof(str), f), sizeof(str));
If that `assertneq` or `asserteq` fails,
we would like and expect to see a line number.
Unfortunately, after the code goes through the preprocessor,
the entire nested macro expansion ends up on a single line.
All line number information is lost.
`__LINE__` just returns the number of the last line of the macro expansion,
which is 14 in this case.
_All_ `__LINE__` expressions inside the block we pass to `describe`
will return the same number.
I have googled around a bunch for a solution to this issue,
but none of the solutions I've looked at actually solve the issue.
The only actual solution I can think of is to write my own preprocessor.
## Some warnings can't be disabled with pragma
Like the above example,
this is probably an issue you shouldn't have come across in production code.
First, some background.
In Snow, both the code which is being tested and the test cases can be in the
same file.
This is to make it possible to test static functions and other functionality
which isn't part of the component's public API.
The idea is that at the bottom of the file, after all non-testing code,
one should include `<snow/snow.h>` and write the test cases.
In a non-testing build,
all the testing code will be removed by the preprocessor,
because the `describe(...)` macro expands to nothing
unless `SNOW_ENABLED` is defined.
My personal philosophy is that your regular builds should _not_ have `-Werror`,
and that your testing builds should have as strict warnings as possible and be
compiled with `-Werror`.
Your users might be using a different compiler version than you are,
and that compiler might produce some warnings which you haven't fixed yet.
Being a user of a rolling release, with a very recent of GCC,
I have way too often had to edit someone else's Makefile and remove `-Werror`
just to make their code compile.
Compiling the test suite with `-Werror` and regular builds without `-Werror`
has none of the drawbacks of using `-Werror` for regular builds, and none of
the drawbacks
(at least if you don't accept contributions which break your test suite).
This all means that I want to be able to compile all files with at least
`-Wall -Wextra -Wpedantic -Werror`, even if the code includes `<snow/snow.h>`.
However, Snow contains code which produces warnings (and therefore errors)
with those settings;
among other things, it uses some GNU extensions
which aren't actually part of the C standard.
I would like to let users of Snow compile their code with at least
`-Wall -Wextra -Wpedantic -Werror`,
but Snow has to disable at least `-Wpedantic` for all code after the inclusion
of the library.
In theory, that shouldn't be an issue, right?
We just include `#pragma GCC diagnostic ignored "-Wpedantic"` somewhere.
Well, as it turns out, disabling `-Wpedantic` with a pragma doesn't disable all
the warnings enabled by `-Wpedantic`;
there are some warnings which are impossible to disable once they're enabled.
One such warning is about using directives (like `ifdef`) inside macro
As I explained earlier,
_everything_ in Snow happens inside of macro arguments.
That means that when compiling with `-Wpedantic`,
this code produces a warning which it's impossible to disable
without removing `-Wpedantic` from the compiler's arguments:
``` C
describe(some_component, {
#ifndef __MINGW32__
it("does something which can't be tested on mingw", {
/* ... */
That's annoying, because it's perfectly legal in GNU's dialect of C.
The only reason we can't do it is that it just so happens to be impossible to
disable that particular warning with a pragma.
To be completely honest, this issue makes complete sense.
I imagine the preprocessor stage, which is where macros are expanded,
doesn't know about pragmas, and implementing pragma parsing for the
preprocessor _just_ in order to let people compile files with `-Wpedantic` but
still selectively disable this particular warning.
That doesn't mean that it's any less annoying though.
Funnily enough, I encountered this issue while writing Snow's test suite.
My solution was to just define
[a macro called NO\_MINGW](
which is empty if `__MINGW32__` is defined,
and expands to the contents of its arguments otherwise.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment