Compile Strict C89 Programs with c89-gcc Without Surprises
You will finish with a repeatable way to compile an ANSI C, or C89, source file using the installed c89-gcc wrapper. You will also check that strict mode is active, produce either an executable or an object file, and tell a source error from a wrapper policy error.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell, a readable C source file and write access to the directory where the compiler will create its output. The examples use GCC 13.3.0, which is what c89-gcc --version reports on this machine. The Debian package metadata identifies the installed gcc package as 4:13.2.0-7ubuntu1; use the compiler's own version output when recording the toolchain used for a build.
Checkpoint
This guide compiles and checks code only. It does not install a compiler, change system headers or require sudo.
1. Check which compiler you are calling
c89 and c89-gcc are the two names exposed by this setup. Both resolve to the GCC-backed shell wrapper, so start by checking the path and version:
$ command -v c89-gcc
/usr/bin/c89-gcc
$ c89-gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
The version banner contains more lines than the example. The useful facts are that the command was found and that it is the expected GCC release. If command -v finds a project-local script or another compiler, stop and inspect your PATH before comparing results with this guide.
2. Compile a small C89 program
Create or choose a source file whose name is shown here as /path/to/hello.c. A safe first build writes a new executable name:
$ c89-gcc /path/to/hello.c -o /path/to/hello-c89
With no diagnostic, GCC returns success and the executable is ready to run. The wrapper adds -std=c89 before passing your arguments to gcc. That selects the C89 dialect and defines __STRICT_ANSI__. It also excludes GNU language features such as inline, typeof and the $ character in identifiers.
Verify both the file type and the program's exit status:
$ file /path/to/hello-c89
/path/to/hello-c89: ELF 64-bit LSB pie executable, x86-64, ...
$ /path/to/hello-c89
$ printf '%s\n' "$?"
0
Your file description may differ with the target architecture or linker defaults. A successful compile does not prove that the program's logic is correct, so run the executable and check its result separately.
3. Confirm that the source sees strict mode
If a build depends on the dialect rather than merely assuming it, ask the preprocessor for its predefined macros:
$ printf '' | c89-gcc -dM -E - | grep '__STRICT_ANSI__'
#define __STRICT_ANSI__ 1
The macro is expanded during preprocessing, so searching for its name in an ordinary -E output is not a useful test. You can also add a source-level check such as printf("%s\n", __STRICT_ANSI__ ? "strict" : "not-strict");; the resulting program prints:
strict
Checkpoint
-E stops after preprocessing and sends the resulting C source to the named file. It does not create an executable. Keep the temporary output in /tmp or another directory intended for generated files.
4. Build an object file for a larger project
When a project has several translation units, compile each one without linking by adding -c:
$ c89-gcc -c /path/to/parser.c -o /path/to/parser.o
$ file /path/to/parser.o
/path/to/parser.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), ...
The object file is not runnable. Link the objects in a later step, still using the wrapper so the final compilation settings remain visible in the build command:
$ c89-gcc /path/to/parser.o /path/to/main.o -o /path/to/parser-tool
Use -fsyntax-only when you only want a source check and do not want an object file or executable:
$ c89-gcc -fsyntax-only /path/to/parser.c
$ printf '%s\n' "$?"
0
5. Make diagnostics stricter when reviewing old code
The wrapper's standard C89 selection is not the same as asking GCC to reject every extension-related issue as an error. Add -pedantic to request warnings for constructs outside the selected ISO dialect, or -pedantic-errors when those diagnostics must fail the build:
$ c89-gcc -pedantic -Wall /path/to/hello.c -o /path/to/hello-c89
$ c89-gcc -pedantic-errors -Wall /path/to/hello.c -o /path/to/hello-c89
These options are passed through to GCC. -Wall enables a useful warning group, but it is not a promise that every defect will be diagnosed. Keep warnings visible in continuous integration and decide separately whether your project treats them as failures.
Do not add a later language standard by accident. The installed wrapper accepts -ansi, -std=c89 and -std=iso9899:1990 as equivalent ways to request the ANSI dialect. If you pass another -std=... option, the wrapper rejects it before invoking GCC. For example:
$ c89-gcc -std=c99 /path/to/hello.c -o /path/to/hello-c89
c89-gcc called with non ANSI/ISO C option -std=c99
Choose gcc directly when you intentionally need C99 or a newer dialect. That is a different build choice, not a repair for a failed C89 build.
6. Diagnose failures without destroying a previous build
Compiler errors are written to standard error. The local manual notes that this wrapper can prefix compile errors with cc:, although the exact prefix depends on the installed wrapper and diagnostic path. Preserve an existing executable by compiling to a new name while investigating:
$ c89-gcc -pedantic-errors /path/to/hello.c -o /path/to/hello-c89.new
$ status=$?
$ printf 'compiler status: %s\n' "$status"
compiler status: 0
If the command fails, read the diagnostic and fix the source or its include paths. Do not replace the working executable until the new command succeeds and you have run the result. Shell redirection or an output path supplied with -o can overwrite a file, so do not use a known-good output name for an experiment.
A missing input is different from a C error. Check it without elevating privileges:
$ test -r /path/to/hello.c && echo readable
$ ls -l /path/to/hello.c
Only use elevated access if the source or destination is deliberately protected by the system. Changing ownership or permissions merely to make a build pass can hide a deployment problem. Prefer copying source into a controlled build directory and fixing the build's ownership rules.
Done means
command -v c89-gccfound the intended wrapper, and its version was recorded.- The source compiled with C89 mode and the resulting program returned the expected status.
- A preprocessing or source-level check confirmed
__STRICT_ANSI__when the build needed that guarantee. - Object-only and syntax-only checks were kept separate from linking.
- Stricter diagnostics used
-pedanticor-pedantic-errorsdeliberately. - Failed experiments used a new output name, leaving any known-good build recoverable.