I saw another post about GCC -Wshadow being too strict. My problem is the opposite.
I revisited some code I'd written and noticed -Wshadow did not work as I expect:
void free(double* x) {
free(x);
}
Or:
#include <string.h>
// other code here
int memset = 0;
Both compile with no warnings with -Wshadow. I believe the usual way is:
::free(x) // for 1st example
::log(x) // when in doubt if it's my function or the log from math.h
However, ideally I'd like to have -Wshadow catch these. I don't think I am understanding GCC or c++ correctly. Could someone please explain why it works this way, and should it? Thank you.
I compile all my code with:
GCC-4.8.2 -static-libstdc++ -std=c++11 -pedantic -Werror -Wall -Wextra -Wshadow -Wold-style-cast
Edited in reply to comments below about overloading: I can also compile
void free(void* x) {
// blah
}
It doesn't look like overloading now, matching exact signature of the C library one. But I think the compiler needs to warn (-Wshadow) in this case. About memset: I'm not actually writing code as blatant as the memset case, but I'm giving it as an example.
Edit2 Example link: https://groups.google.com/forum/#!topic/android-ndk/8v2M1i1-Dls
In this link's post, you cannot declare symbols like "abs" with -Wshadow. Yet I can declare symbols like "memset", "free", etc. GCC site says -Wshadow should warn when built-ins are shadowed.
Shadowing warnings are targeted at situations when: 1) the name hiding occurs, i.e. one name is hidden by another name declared later or in a more local scope, 2) the code is still well-formed.
In case of your free, if the declaration is made in global namespace, then you have overloading, not shadowing. (I can't say right away whether this attempt to "extend" the standard library is actually legal.) In case of your memset, again if the declaration is made in global namespace, the declaration is simply illegal, i.e. the code is invalid.
As for contexts when genuine name hiding actually happens... You see, -Wshadow is intended to be a warning - something that is intended to catch potentially dangerous name hiding in formally perfectly legal code. As is always the case with warnings, in order to avoid making it overly zealous and annoying its developers had to somehow separate all name hiding situations into those that look really suspicious and those which are likely to be intentional. For this specific warning, I'd say that it would make sense to warn when hiding happens between entities of similar nature: a type hides another type, a function hides another function and a variable hides another variable. When entities of the same nature hide each other, there is a great potential for error: the user might use one entity while believing they use the other, and the code will still quietly compile. But when entities of different natures hide each other, the need to warn is greatly reduced, since entities of different nature have quite different usage contexts/patterns/syntaxes. For example, variable usage syntax is usually different from function usage syntax (this is not entirely accurate, of course, if we consider function objects, but still largely true). Any accidental misuse will likely be caught by the compiler as an error. For this reason, it is probably a good idea not to issue a shadowing warning when, say, a variable hides a function or a function hides a type. This is probably why you get no warning from -Wshadow when a local memset variable hides the global memset function. If you accidentally miss or forget about the local int memset; declaration and continue to use memset as if it were a function (memset(dst, src, n)) the code will simply fail to compile. No need for any extra warnings here, since your error will most definitely be caught and reported anyway.
UPDATE: In your additional case with free declared as void free(void *), i.e. fully matching the standard function signature, you are dealing with one of gcc "features". You are actually providing your own definition of a standard library function, which replaces the "standard" definition. Specifically to give you that opportunity all standard library functions in GCC library implementation are declared as so called weak symbols. This is a useful feature in some cases (as a debugging aid and such), but the price we have to pay for it is that it becomes possible to accidentally replace a standard function.
Whether -Wshadow is supposed to warn about this is a different question... Again, -Wshadow was introduced to warn about dangerous name hiding. In this case there's no name hiding and, formally, the code is illegal. But GCC allows it for the purposes outlined above.
Related
I just found this line of code within a function, which puzzles me. Can this make sense in any context or it is undefined behavior?
char * acFilename = acFilename;
EDIT: The compiler complains with Warning C4700, that I am using an uninitialized variable.
At block scope, in C++, this is undefined behaviour, as the right-hand side reads the variable the variable before it has been initialized (C++14 [dcl.init]/12).
At block scope, in C11, this could either be undefined behaviour or behave as an uninitialized variable, depending on various details of the implementation and the rest of the function, see here for detailed analysis.
At namespace scope, in C++, it is OK well-defined and makes a null pointer. This is because all static variables are zero-initialized before their initializers are considered. (C++14 [basic.start.init]/2).
At file scope in C, it is a constraint violation; static variables must have a constant expression as initializer, and the value of a variable cannot be a constant expression.
No this code does not make any sense. It's probably a typo, maybe somebody meant to use
char* acFilename = ::acFilename;
or
char* acFilename = m_acFilename;
or something else.
As it stands, it's confusing and unhelpful at best, probably a bug because somebody meant to use a different variable.
I've seen this before. Since gcc is quite trigger happy with its uninitialized variable warnings this is a trick to silence those warnings. Not sure if it's intentional design choice by gcc or the compiler just being stupid, but I've seen people do this on purpose to make gcc shut up (and in the process break the warning that might be correct in the future).
I would just replace it with an initialization to NULL. This is a bad habit, doesn't work on other compilers and a NULL can't be less correct than an indeterminate value and undefined behavior.
Just to demonstrate how this works (and also because I wanted to know if newer gcc versions still do this):
$ cat foo.c
int
foobar(void)
{
int foo;
return foo + foo;
}
$ cc -c -Wall -O2 foo.c
foo.c: In function ‘foobar’:
foo.c:6:13: warning: ‘foo’ is used uninitialized in this function [-Wuninitialized]
return foo + foo;
~~~~^~~~~
$ ed foo.c
[...]
$ cc -c -Wall -O2 foo.c
$ cat foo.c
int
foobar(void)
{
int foo = foo;
return foo + foo;
}
$ cc -c -Wall -O2 foo.c
$ cc -v
[...]
gcc version 6.2.0 20161005 (Ubuntu 6.2.0-5ubuntu12)
$
At some locations within a program, it may be possible for a variable to be in either of the following conditions:
Inputs that have been received have caused the variable to be written, and will also cause code to use its value in future.
Inputs that have been received have caused the variable not to be written, but will also cause code not to make any use of its value.
Some compilers will squawk if they see that there exist inputs which would cause a variable to not to get written, and there exist inputs which would cause the variable to be read. Even if the only input which would cause the variable to get read would also cause it to get written, the compilers may not realize that. While it would be possible to silence the compiler's complaints by unconditionally initializing the variable, machine code which initializes the variable with a value which can never actually get used would serve no functional purpose. A source code construct which would silence the compiler's complaints but not result in the compiler generating useless machine code may thus be preferable to one which would need to generate useless machine code. On some compilers, a self-initializing declaration may serve such a purpose.
If a type has trap representations, there may be no practical alternative to writing an initialization that a compiler will likely turn into useless machine code, but if a type doesn't have trap representations there is no particular reason why a quality compiler should force programmers to ask for useless code they don't want and don't need. Rather than try to have different rules for platforms where various types do and don't have trap representations, however, the authors of the Standard defer to implementers' judgments as to what sorts of behavior would be sensible on for a particular target platform and application field.
If on some platform a compiler couldn't allow programmers to omit initialization for variables which are copied but otherwise unused without having to generate likely-redundant initializations itself, it would make sense to require the programmer to include initializations in all situations that could actually occur. On another platform where copying a meaningless value would simply cause the destination to hold a meaningless value, however, it would generally make more sense to allow a programmer to omit initialization in cases where none of the copies would ever end up getting used for any real purpose. The authors of the Standard make no effort to specify what would make sense in any particular case, because they expect that people writing implementations should be capable of exercising good judgment.
The best approach for dealing with this situation would probably be to write a macro which accepts a variable, and whose expansion will initialize the variable to zero (for implementations whose authors judge that there is more value in allowing the compilers to jump the rails when an uninitialized variable is copied, even if none of the copies are really "used" for anything, than there would be in guaranteeing that the mere act of copying a variable will have no side-effects) or else do nothing (when using an implementation which will stay on the rails even without the useless initialization).
Is there a magic incantation to keep gcc from complaining if the argument for the %p format specifier is not a void*?
A pragma would be ideal because I could put that in my standard compiler config header and so save the problem once and for all, instead of having to fiddle with project/make files every time...
Normally I prefer the equivalent of -Wall -Wextra -Wpedantic with all compilers because warnings on clean source often indicate a mistake on my part, or that the compiler may be losing the plot. However, I can see no possible benefit in cluttering my source code with superfluous casts (C style no less, which I haven't used in years, or the extra-long hyper-cluttering ones), so this particular warning needs to go away.
Addendum: I know that typedefs and/or templates can reduce the inline clutter to one letter and a pair of parens but they require extra out-of-line clutter. That stuff is noise, it reduces clarity and expressiveness instead of improving them.
However, it is my choice not to want this warning, and my goal to make it go away. It is rather silly anyway, since actually occurring problems with pointers (near, far, based, whatever) are orthogonal to the void-ness of the pointer... And to preempt other potential objections, I'm talking about plain data pointers, not function pointers, pointers to members, closures or whatnot. Should I ever need to port something to an oddball platform like Konrad Zuse's Z1 then I can adapt the compiler config header to not suppress that warning on that particular platform...
There doesn't appear to be such an option. The -Wformat family of options to GCC are documented here (search for -Wformat). If there were a switch for what you want to do, I'd expect it to be found here.
This question already has answers here:
Why does flowing off the end of a non-void function without returning a value not produce a compiler error?
(11 answers)
Closed 6 years ago.
I found this in one of my libraries this morning:
static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out)
{
tvec3::Min(a,b,out);
out.w = min(a.w,b.w);
}
I'd expect a compiler error because this method doesn't return anything, and the return type is not void.
The only two things that come to mind are
In the only place where this method is called, the return value isn't being used or stored. (This method was supposed to be void - the tvec4 return type is a copy-and-paste error)
a default constructed tvec4 is being created, which seems a bit unlike, oh, everything else in C++.
I haven't found the part of the C++ spec that addresses this. References (ha) are appreciated.
Update
In some circumstances, this generates an error in VS2012. I haven't narrowed down specifics, but it's interesting, nonetheless.
This is undefined behavior from the C++11 draft standard section 6.6.3 The return statement paragraph 2 which says:
[...] Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function. [...]
This means that the compiler is not obligated provide an error nor a warning usually because it can be difficult to diagnose in all cases. We can see this from the definition of undefined behavior in the draft standard in section 1.3.24 which says:
[...]Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).[...]
Although in this case we can get both gcc and clang to generate a wanring using the -Wall flag, which gives me a warning similar to this:
warning: control reaches end of non-void function [-Wreturn-type]
We can turn this particular warning into an error using the -Werror=return-type flag. I also like to use -Wextra -Wconversion -pedantic for my own personal projects.
As ComicSansMS mentions in Visual Studio this code would generate C4716 which is an error by default, the message I see is:
error C4716: 'Min' : must return a value
and in the case where not all code paths would return a value then it would generate C4715, which is a warning.
Maybe some elaboration on the why part of the question:
As it turns out, it is actually quite hard† for a C++ compiler to determine whether a function exits without a return value. In addition to the code paths that end in explicit return statements and the ones that fall off the end of the function, you also have to consider potential exception throws or longjmps in the function itself, as well as all of its callees.
While it is quite easy for a compiler to identify a function that looks like it might be missing a return, it is considerably harder to prove that it is missing a return. In order to lift compiler vendors of this burden, the standard does not require this to generate an error.
So compiler vendors are free to generate a warning if they are quite sure that a function is missing a return and the user is then free to ignore/mask that warning in those rare cases where the compiler was actually wrong.
†: In the general case, this is equivalent to the halting problem, so it is actually impossible for a machine to decide this reliably.
Compile your code with -Wreturn-type option:
$ g++ -Wreturn-type source.cpp
This will give you warning. You can turn the warning into error if you use -Werror too:
$ g++ -Wreturn-type -Werror source.cpp
Note that this will turn all warnings into errors. So if you want error for specific warning, say -Wreturn-type, just type return-type without -W part as:
$ g++ -Werror=return-type source.cpp
In general you should always use -Wall option which includes most common warnings — this includes missing return statement also. Along with -Wall, you can use -Wextra also, which includes other warnings not included by -Wall.
Maybe some additional elaboration on the why part of the question.
C++ was designed so that a very large body of pre-existing body of C code compiles with minimum amount of changes. Unfortunately, C itself was paying a similar duty to earliest pre-standard C which did not even have the void keyword and instead relied on a default return type of int. C functions usually did return values, and whenever code superficially similar to Algol/Pascal/Basic procedures was written without any return statements, the function was, under the hood, returning whichever garbage was left on the stack. Neither the caller nor the callee assigns the value of the garbage in a reliable way. If the garbage is then ignored by every caller, everything is fine and C++ inherits the moral obligation to compile such code.
(If the returned value is used by the caller, the code may behave non-deterministically, similar to processing of an uninitialized variable. Could the difference be reliably identified by a compiler, in a hypothetical successor language to C? This is hardly possible. The caller and the callee may be in different compilation units.)
The implicit int is just a part of the C legacy involved here. A "dispatcher" function might, depending on a parameter, return a variety of types from some code branches, and return no useful value from other code branches. Such a function would generally be declared to return a type long enough to hold any of the possible types and the caller might need to cast it or extract it from a union.
So the deepest cause is probably the C language creators' belief that procedures that do not return any value are just an unimportant special case of functions that do; this problem got aggravated by the lack of focus on type safety of function calls in the oldest C dialects.
While C++ did break compatibility with some of the worst aspects of C (example), the willingness to compile a return statement without a value (or the implicit value-less return at the end of a function) was not one of them.
As already mentioned, this is undefined behavior and will give you a compiler warning. Most places I've worked require you to turn on compiler settings to treat warnings as errors - which enforces that all your code must compile with 0 errors and 0 warnings. This is a good example of why that is a good idea.
This is more of the standard C++ rule/feature which tends to be flexible with things and which tends to be more close to C.
But when we talk of the compilers, GCC or VS, they are more for professional usage and for variety of development purposes and hence put more strict development rules as per your needs.
That makes sense also, my personal opinion, because the language is all about features and its usage whereas compiler defines the rules for optimal and best way of using it as per your needs.
As mentioned in above post, compiler sometimes gives the error, sometimes gives warning and also it has the option of skipping these warning etc, indicating the freedom to use the language and its features in a way that suits us best.
Along with this there are several other questions mentioning this behaviour of returning a result without having a return statement. One simple example would be:
int foo(int a, int b){ int c = a+b;}
int main(){
int c = 5;
int d = 5;
printf("f(%d,%d) is %d\n", c, d, foo(c,d));
return 0;
}
Could this anomaly be due stack properties and more specifically:
Zero-Address Machines
In zero-address machines, locations of both operands are assumed to be at a default location.
These machines use the stack as the source of the input operands and the result goes back into
the stack. Stack is a LIFO (last-in-first-out) data structure that all processors support, whether
or not they are zero-address machines. As the name implies, the last item placed on the stack
is the first item to be taken out of the stack. All operations on this type of machine assume that the required input operands are the top
two values on the stack. The result of the operation is placed on top of the stack.
In addition to that, for accessing memory to read and write data same registers are used as data source and destination(DS (data segment) register), that store first the variables needed for the calculation and then the returned result.
Note:
with this answer I would like to discuss one possible explanation of the strange behaviour at machine (instruction) level as it has already a context and its covered in adequately wide range.
This question already has answers here:
Why does flowing off the end of a non-void function without returning a value not produce a compiler error?
(11 answers)
Closed 6 years ago.
I found this in one of my libraries this morning:
static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out)
{
tvec3::Min(a,b,out);
out.w = min(a.w,b.w);
}
I'd expect a compiler error because this method doesn't return anything, and the return type is not void.
The only two things that come to mind are
In the only place where this method is called, the return value isn't being used or stored. (This method was supposed to be void - the tvec4 return type is a copy-and-paste error)
a default constructed tvec4 is being created, which seems a bit unlike, oh, everything else in C++.
I haven't found the part of the C++ spec that addresses this. References (ha) are appreciated.
Update
In some circumstances, this generates an error in VS2012. I haven't narrowed down specifics, but it's interesting, nonetheless.
This is undefined behavior from the C++11 draft standard section 6.6.3 The return statement paragraph 2 which says:
[...] Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function. [...]
This means that the compiler is not obligated provide an error nor a warning usually because it can be difficult to diagnose in all cases. We can see this from the definition of undefined behavior in the draft standard in section 1.3.24 which says:
[...]Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).[...]
Although in this case we can get both gcc and clang to generate a wanring using the -Wall flag, which gives me a warning similar to this:
warning: control reaches end of non-void function [-Wreturn-type]
We can turn this particular warning into an error using the -Werror=return-type flag. I also like to use -Wextra -Wconversion -pedantic for my own personal projects.
As ComicSansMS mentions in Visual Studio this code would generate C4716 which is an error by default, the message I see is:
error C4716: 'Min' : must return a value
and in the case where not all code paths would return a value then it would generate C4715, which is a warning.
Maybe some elaboration on the why part of the question:
As it turns out, it is actually quite hard† for a C++ compiler to determine whether a function exits without a return value. In addition to the code paths that end in explicit return statements and the ones that fall off the end of the function, you also have to consider potential exception throws or longjmps in the function itself, as well as all of its callees.
While it is quite easy for a compiler to identify a function that looks like it might be missing a return, it is considerably harder to prove that it is missing a return. In order to lift compiler vendors of this burden, the standard does not require this to generate an error.
So compiler vendors are free to generate a warning if they are quite sure that a function is missing a return and the user is then free to ignore/mask that warning in those rare cases where the compiler was actually wrong.
†: In the general case, this is equivalent to the halting problem, so it is actually impossible for a machine to decide this reliably.
Compile your code with -Wreturn-type option:
$ g++ -Wreturn-type source.cpp
This will give you warning. You can turn the warning into error if you use -Werror too:
$ g++ -Wreturn-type -Werror source.cpp
Note that this will turn all warnings into errors. So if you want error for specific warning, say -Wreturn-type, just type return-type without -W part as:
$ g++ -Werror=return-type source.cpp
In general you should always use -Wall option which includes most common warnings — this includes missing return statement also. Along with -Wall, you can use -Wextra also, which includes other warnings not included by -Wall.
Maybe some additional elaboration on the why part of the question.
C++ was designed so that a very large body of pre-existing body of C code compiles with minimum amount of changes. Unfortunately, C itself was paying a similar duty to earliest pre-standard C which did not even have the void keyword and instead relied on a default return type of int. C functions usually did return values, and whenever code superficially similar to Algol/Pascal/Basic procedures was written without any return statements, the function was, under the hood, returning whichever garbage was left on the stack. Neither the caller nor the callee assigns the value of the garbage in a reliable way. If the garbage is then ignored by every caller, everything is fine and C++ inherits the moral obligation to compile such code.
(If the returned value is used by the caller, the code may behave non-deterministically, similar to processing of an uninitialized variable. Could the difference be reliably identified by a compiler, in a hypothetical successor language to C? This is hardly possible. The caller and the callee may be in different compilation units.)
The implicit int is just a part of the C legacy involved here. A "dispatcher" function might, depending on a parameter, return a variety of types from some code branches, and return no useful value from other code branches. Such a function would generally be declared to return a type long enough to hold any of the possible types and the caller might need to cast it or extract it from a union.
So the deepest cause is probably the C language creators' belief that procedures that do not return any value are just an unimportant special case of functions that do; this problem got aggravated by the lack of focus on type safety of function calls in the oldest C dialects.
While C++ did break compatibility with some of the worst aspects of C (example), the willingness to compile a return statement without a value (or the implicit value-less return at the end of a function) was not one of them.
As already mentioned, this is undefined behavior and will give you a compiler warning. Most places I've worked require you to turn on compiler settings to treat warnings as errors - which enforces that all your code must compile with 0 errors and 0 warnings. This is a good example of why that is a good idea.
This is more of the standard C++ rule/feature which tends to be flexible with things and which tends to be more close to C.
But when we talk of the compilers, GCC or VS, they are more for professional usage and for variety of development purposes and hence put more strict development rules as per your needs.
That makes sense also, my personal opinion, because the language is all about features and its usage whereas compiler defines the rules for optimal and best way of using it as per your needs.
As mentioned in above post, compiler sometimes gives the error, sometimes gives warning and also it has the option of skipping these warning etc, indicating the freedom to use the language and its features in a way that suits us best.
Along with this there are several other questions mentioning this behaviour of returning a result without having a return statement. One simple example would be:
int foo(int a, int b){ int c = a+b;}
int main(){
int c = 5;
int d = 5;
printf("f(%d,%d) is %d\n", c, d, foo(c,d));
return 0;
}
Could this anomaly be due stack properties and more specifically:
Zero-Address Machines
In zero-address machines, locations of both operands are assumed to be at a default location.
These machines use the stack as the source of the input operands and the result goes back into
the stack. Stack is a LIFO (last-in-first-out) data structure that all processors support, whether
or not they are zero-address machines. As the name implies, the last item placed on the stack
is the first item to be taken out of the stack. All operations on this type of machine assume that the required input operands are the top
two values on the stack. The result of the operation is placed on top of the stack.
In addition to that, for accessing memory to read and write data same registers are used as data source and destination(DS (data segment) register), that store first the variables needed for the calculation and then the returned result.
Note:
with this answer I would like to discuss one possible explanation of the strange behaviour at machine (instruction) level as it has already a context and its covered in adequately wide range.
Can't a compiler warn (even better if it throws errors) when it notices a statement with undefined/unspecified/implementation-defined behaviour?
Probably to flag a statement as error, the standard should say so, but it can warn the coder at least. Is there any technical difficulties in implementing such an option? Or is it merely impossible?
Reason I got this question is, in statements like a[i] = ++i; won't it be knowing that the code is trying to reference a variable and modifying it in the same statement, before a sequence point is reached.
It all boils down to
Quality of Implementation: the more accurate and useful the warnings are, the better it is. A compiler that always printed: "This program may or may not invoke undefined behavior" for every program, and then compiled it, is pretty useless, but is standards-compliant. Thankfully, no one writes compilers such as these :-).
Ease of determination: a compiler may not be easily able to determine undefined behavior, unspecified behavior, or implementation-defined behavior. Let's say you have a call stack that's 5 levels deep, with a const char * argument being passed from the top-level, to the last function in the chain, and the last function calls printf() with that const char * as the first argument. Do you want the compiler to check that const char * to make sure it is correct? (Assuming that the first function uses a literal string for that value.) How about when the const char * is read from a file, but you know that the file will always contain valid format specifier for the values being printed?
Success rate: A compiler may be able to detect many constructs that may or may not be undefined, unspecified, etc.; but with a very low "success rate". In that case, the user doesn't want to see a lot of "may be undefined" messages—too many spurious warning messages may hide real warning messages, or prompt a user to compile at "low-warning" setting. That is bad.
For your particular example, gcc gives a warning about "may be undefined". It even warns for printf() format mismatch.
But if your hope is for a compiler that issues a diagnostic for all undefined/unspecified cases, it is not clear if that should/can work.
Let's say you have the following:
#include <stdio.h>
void add_to(int *a, int *b)
{
*a = ++*b;
}
int main(void)
{
int i = 42;
add_to(&i, &i); /* bad */
printf("%d\n", i);
return 0;
}
Should the compiler warn you about *a = ++*b; line?
As gf says in the comments, a compiler cannot check across translation units for undefined behavior. Classic example is declaring a variable as a pointer in one file, and defining it as an array in another, see comp.lang.c FAQ 6.1.
Different compilers trap different conditions; most compilers have warning level options, GCC specifically has many, but -Wall -Werror will switch on most of the useful ones, and coerce them to errors. Use \W4 \WX for similar protection in VC++.
In GCC You could use -ansi -pedantic, but pedantic is what it says, and will throw up many irrelevant issues and make it hard to use much third party code.
Either way, because compilers catch different errors, or produce different messages for the same error, it is therefore useful to use multiple compilers, not necessarily for deployment, but as a poor-man's static analysis. Another approach for C code is to attempt to compile it as C++; the stronger type checking of C++ generally results in better C code; but be sure that if you want C compilation to work, don't use the C++ compilation exclusively; you are likely to introduce C++ specific features. Again this need not be deployed as C++, but just used as an additional check.
Finally, compilers are generally built with a balance of performance and error checking; to check exhaustively would take time that many developers would not accept. For this reason static analysers exist, for C there is the traditional lint, and the open-source splint. C++ is more complex to statically analyse, and tools are often very expensive. One of the best I have used is QAC++ from Programming Research. I am not aware of any free or open source C++ analysers of any repute.
gcc does warn in that situation (at least with -Wall):
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[5];
int i = 0;
a[i] = ++i;
printf("%d\n", a[0]);
return 0;
}
Gives:
$ make
gcc -Wall main.c -o app
main.c: In function ‘main’:
main.c:8: warning: operation on ‘i’ may be undefined
Edit:
A quick read of the man page shows that -Wsequence-point will do it, if you don't want -Wall for some reason.
Contrarily, compilers are not required to make any sort of diagnosis for undefined behavior:
§1.4.1:
The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”
Emphasis mine. While I agree it may be nice, the compiler's have enough problem trying to be standards compliant, let alone teach the programmer how to program.
GCC warns as much as it can when you do something out of the norms of the language while still being syntactically correct, but beyond the certain point one must be informed enough.
You can call GCC with the -Wall flag to see more of that.
If your compiler won't warn of this, you can try a Linter.
Splint is free, but only checks C http://www.splint.org/
Gimpel Lint supports C++ but costs US $389 - maybe your company c an be persuaded to buy a copy? http://www.gimpel.com/