|
|
| Stage | Compiler Options | Description |
|---|---|---|
| 1 | -std= | Verify the language dialect to which you are writing. |
| 2 | -ansi -pedantic |
Enforce extra language-level checks and compliance. |
| 3 | -Wall |
Report the most common programming errors. |
| 4 | -Wall, -Wmost or -Wextra |
Report the most common programming errors and less-serious but potential problems. |
| 5 | -Wno-div-by-zero -Wsystem-headers -Wfloat-equal -Wtraditional (C only) -Wdeclaration-after-statement (C only) -Wundef -Wno-endif-labels -Wshadow -Wlarger-than-len -Wpointer-arith -Wbad-function-cast (C only) -Wcast-qual -Wcast-align -Wwrite-strings -Wconversion -Wsign-compare -Waggregate-return -Wstrict-prototypes (C only) -Wold-style-definition (C only) -Wmissing-prototypes (C only) -Wmissing-declarations (C only) -Wmissing-field-initializers -Wmissing-noreturn -Wmissing-format-attribute -Wno-multichar -Wno-deprecated-declarations -Wpacked -Wpadded -Wredundant-decls -Wnested-externs (C only) -Wunreachable-code -Winline -Wno-invalid-offsetof (C++ only) -Winvalid-pch -Wlong-long -Wvariadic-macros -Wdisabled-optimization -Wno-pointer-sign |
Provide additional checks not covered by -Wall -Wextra or -Wmost |
Table 1: Stages of static checks
Stage 1 options check your code for adherence to the particular language dialect. Too often, we write code and compile it with the default language dialect. In some cases, this is fine. But it’s always better to tell the compiler up front the language dialect you are targeting.
Note: For C applications, always set the dialect when you create the project. For Objective-C (Cocoa-based) applications, the default dialect is fine.
The benefit of this stage is that the compiler catches anything outside the targeted language dialect. Remember, GCC does not guarantee complete compliance with C language standards. But it will give you a good start. (See the GCC manual for more information on the language dialects and the exceptions for the different standards.)
Stage 2 provides stricter language dialect checking. By using the -ansi
and -pedantic options (you enable the Pedantic warning through the GUI),
GCC enforces more complete language dialect compliance.
As you know, some of the GCC language dialects enable GNU language extensions (GNU89
and GNU99). This is acceptable as long as you do not need strict ISO C language
compliance. If you do, you can use the -ansi and -pedantic
options in combination with the dialect setting.
The -ansi option disables all GNU extensions that conflict with the
selected language dialect. As a result, you can write programs to a particular ISO C
dialect without unwanted effects from GNU extensions. The -pedantic
option extends the coverage. You use the -pedantic option together with
-ansi to reject all GNU extensions—not just extensions that are
incompatible with the language dialect.
By joining the dialect option with -ansi and -pedantic,
you can write programs that conform (with some exceptions) to the language dialect
of your choice. The benefit of this stage is much stricter checking of language
conformance.
The two most commonly used warning options are -Wall and
-Wextra/-Wmost. These options are mnemonics that aggregate
a set of options under a single option. Xcode does not enable these options by
default, however. You must enter them yourself under Other Warning Flags.
The -Wall option enables warning options for the most common
programming errors. It reports constructs that are always wrong and therefore
need your attention. If a build produces a warning message with this flag on,
consider it a serious problem and fix it right away. The conventional wisdom
is to enable this option for all builds.
The -Wextra option detects less serious problems. (This option was
called -W in earlier versions of GCC. GCC still supports this syntax,
but you should consider it deprecated and use -Wextra, instead.).
This option detects many classes of constructs that are technically valid but
may cause problems if left unchecked.
For example, compiling the code in Listing 4 with just the -Wall
option does not produce a warning. However, if you add -Wextra,
the problems are detected.
Listing 4: Code compiled with the -Wall
option
void checkVal(unsigned int n) {
if (n < 0) {
/* Do something... */
}
else if (n >= 0) {
/* Do something else... */
}
}
Both -Wextra and -Wall are standard GCC options that
you can use across compiler implementations. The -Wmost option
is an Apple-specific flag equivalent to the -Wall option but
disabling -Wparentheses (-Wno-parentheses).
Many developers have mixed feelings about enabling -Wextra for
all builds because it detects some false positives. That said, a good
recommendation is always to use it, or at least to enable it before you check
in your code. It is better to know where you stand upstream in the development
process than to let even one error slip past that can hurt you later.
Turning on -Wall or -Wextra/-Wmost
gives you moderate checking for your applications. However, GCC supports other
options that increase your level of confidence in your code.
Listing 5 shows an Objective-C application that does not generate any warning messages under Stage 4.
Listing 5: Objective-C application with Stage-4 options enabled
// main.m
#import <Cocoa/Cocoa.h>
#import "ClassA.h"
int main(void) {
ClassA *obj = [[ClassA alloc] init];
double x = 10.0;
double y = 11.0;
double z = 0.0;
z = [obj process:x :y];
(void)printf("%lf\n", z);
[obj release];
return 0;
}
// ClassA.h
#import <Cocoa/Cocoa.h>
@interface ClassA : NSObject {
}
-(double) process:(double)x: (double)y;
@end
// ClassA.m
#import "ClassA.h"
@implementation ClassA
- (id) init {
self = [super init];
return self;
}
- (void) dealloc {
[super dealloc];
}
-(double) process:(double)x: (double)y {
double z = 0.0;
if (x == y) {
z = x * y;
return z;
}
else {
z = x * y;
return z;
}
z = x - y;
return z;
}
@end
This is where adding some extra static checking can really help. To add
more checking, add -Wfloat-equal and -Wunreachable-code
to the Other Warning Flags list. The checks you enabled in Stage 5 provide
the most complete static checking that GCC offers (see Figure 5).
Figure 5. Stage-5 static analysis results on Listing 5
In addition to language dialect and warning options, GCC offers other options that give you control over how warning and error information is displayed. Table 2 lists these options.
| Compiler Options | Description |
|---|---|
| -fsyntax-only | Check for syntax errors only and do not compile or build code. |
| -pedantic-errors | Report all pedantic warnings (-pedantic) as errors. |
| -w | Disable all warning messages. |
| -Wfatal-errors | Abort the compilation on the first error. |
| -Wsystem-headers | Report warning messages found in system header files. |
| -Werror | Report all warnings as errors. |
Table 2: Display options
You can use the C language-independent options for checking your Objective-C code. However, GCC also supports several warning options specific to the Objective-C language.
In object-oriented programming, it is common to define an interface for behavior that decedents will implement. The interface does not contain an implementation, just a placeholder that others fill in with an implementation specific to their needs.
To get this functionally in Java code, you use interfaces; in C++, abstract classes. Objective-C provides it through protocols. There are two types of protocols: formal and informal. When a class conforms to another class’s formal protocol, that class must implement all methods in the protocol. An informal protocol, on the other hand, defines a set of methods that a class should implement but are not required.
Listing 6 shows an example of a protocol and a class that implements the protocol.
Listing 6: A protocol and its implementation
// MyProto.h
@protocol MyProto
-(void) doThis;
-(void) doThat;
@end
// ClassA.h
#import <Cocoa/Cocoa.h>
#import "MyProto.h"
@interface ClassA: NSObject {
}
-(void) doThis;
-(void) doThat;
@end
// ClassA.m
#import "ClassA.h"
@implementation ClassA
-(void) doThis {
(void)printf("ClassA:doThis\n");
}
-(void) doThat {
(void)printf("ClassA:doThat\n");
}
@end
MyProto is a formal protocol that defines a list of methods. Any class that
wants to conform to MyProto must implement these methods, as ClassA does.
GCC will not report any warnings when compiling this code under the default Cocoa
application build.
Now, imagine that you extend the implementation as shown in Listing 7 and Figure 6.
Listing 7: Code to extend the implementation of ClassB
// ClassB.h
#import <Cocoa/Cocoa.h>
#import "ClassA.h"
@interface ClassB : ClassA {
}
@end
// ClassB.m
#import "ClassB.h"
@implementation ClassB
@end
Figure 6. The MyProto protocol and its implementation
Building this code produces four related compiler warnings (see Figure 7).
Figure 7. Compiler warnings on the ClassB build
GCC reports these warning because the default build enables the
Incomplete Objective-C Protocols Warn option. By setting this option,
you are telling GCC to report a warning if methods that a protocol requires are
not implemented in the class adopting it.
The problem is that ClassB inherits the implementation of the protocol methods from its superclass (ClassA) instead of implementing them itself. One way to address this behavior is to disable the option. Unfortunately, the compiler will not warn you about legitimate protocol problems in your code. (See section 3.6, “Options Controlling Objective-C and Objective-C++ Dialects,” of the GCC manual for more information.)
Now that you understand the basics of using GCC and Xcode for statically verifying your C and Objective-C code, it’s time to put your knowledge into action. You need a simple way to build a code base with Stage 1–5 checks. Happily, Xcode supports such a configuration with Targets and Build Configuration.
A target is a high-level container, or blueprint, that aggregates files, source and header files, libraries, build settings, compiler flags, and rules that state how files are processed. A build configuration is a way to collect specific build settings under a name and apply them to a target. Through build configurations, you can customize your builds for a single code base without having to create separate targets for each product. You can have one target for your project and different build configurations, each customized to a different build level.
To create a custom build configuration, click the Targets disclosure triangle (under the Groups & Files list), Control-click a target, and then click Get Info. In the Target Info dialog, click the Build tab, and then choose Edit Configurations from the Configurations menu (Figures 8a and 8b).
Figure 8a. Create a custom build configuration
Figure 8b. Create a custom build configuration
To create a new build setting, select an existing setting (Debug, for example), and then click Duplicate. Rename the setting to your custom build configuration. Then, choose the new build configuration from the Target Info window’s Configuration menu and customize it to your needs (see Figures 9a and 9b).
Figure 9a. Create a new build setting
Figure 9b. Create a new build setting
To use your new build setting, open the Build Results window (choose Window > Tools > Build Results), choose the configuration you want to apply from the Active Build Configuration menu, and then build the project (see Figure 10). (If the Active Build Configuration menu does not appear in the toolbar, you must add it.)
Figure 10. Apply your new build setting
Xcode uses these settings to build your project.
This article has only touched the surface of what you can do with static analysis techniques. Hopefully, it has gotten you excited about using GCC’s static analysis options and integrating static analysis techniques into your development cycle. By combining GCC’s language dialect and warning options with Xcode’s intuitive interface, you can drastically improve the quality of your code with little effort. If you want even more extensive checking of your C applications, try Splint.
Posted: 2006-07-10