Legacy C at Work: Turbo C, gets & K&R Habits
Many students in India first meet C in a college lab running Turbo C, a DOS compiler from the late 1980s and early 1990s, or in exam papers and guidebooks written for it. Then, at work, they meet code that is decades old: billing systems, device drivers, banking batch jobs and vendor SDKs written in C89 or even K&R style. Both are real, and a working C programmer must be able to read them, explain what is wrong, and modernise them without breaking anything.
In this lesson you will dissect a typical Turbo C program, learn which habits are non-standard or dangerous and why, recognise the older language features you will read in company code, and follow a safe process for building and updating legacy code with a modern compiler. C Standards: K&R to C23 covers the history; this lesson is about the practical side.
A typical Turbo C lab program
This program would be accepted in many labs. Every marked line is a problem in modern C:
#include <stdio.h>
#include <conio.h> /* 1. not standard C */
void main() /* 2. main must return int */
{
int marks[5], i, total = 0;
char name[20];
clrscr(); /* 3. Turbo C only */
printf("Enter name: ");
gets(name); /* 4. removed from C in 2011 */
for (i = 0; i < 5; i++) {
printf("Marks %d: ", i + 1);
scanf("%d", &marks[i]); /* 5. result never checked */
fflush(stdin); /* 6. undefined behaviour */
total = total + marks[i];
}
printf("%s scored %d", name, total);
getch(); /* 7. Turbo C only: waits for a key */
}| # | Habit | Why it is a problem | Modern replacement |
|---|---|---|---|
| 1 | conio.h | A Borland/DOS header, not part of C; GCC on Linux does not have it | Remove it; use only standard headers |
| 2 | void main() | Standard C requires int main(void) (or with argc, argv) for portable programs; the exit status is lost | int main(void) and return 0; |
| 3 | clrscr() | Clears a DOS screen; does not exist elsewhere | Do not clear the screen; plain output is easier to test |
| 4 | gets(name) | No size limit, so long input overflows name; removed in C11 | fgets(name, sizeof name, stdin) |
| 5 | Unchecked scanf | Typing letters leaves marks[i] unset and the loop misbehaves | fgets plus strtol, with checks |
| 6 | fflush(stdin) | The standard defines fflush only for output streams; on input it is undefined (Microsoft's library happens to clear the buffer) | Read whole lines with fgets so nothing is left over |
| 7 | getch() | Kept the DOS window open; not standard | Run programs from a terminal, which stays open |
Two more Turbo C facts matter. Turbo C was a 16-bit compiler, so its int was 2 bytes with a maximum of 32,767; textbooks written for it say "int is 2 bytes", which is wrong on every modern PC, where it is 4. And Turbo C's graphics.h (the BGI graphics library) is not part of C at all; modern code uses libraries such as SDL or raylib for graphics.
The same program in modern C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool read_line(const char *prompt, char *buf, size_t size)
{
printf("%s", prompt);
fflush(stdout); // fflush on OUTPUT is fine
if (fgets(buf, (int) size, stdin) == nullptr) {
return false; // input ended
}
buf[strcspn(buf, "\n")] = '\0';
return true;
}
static bool parse_marks(const char *text, int *out)
{
char *end;
long value = strtol(text, &end, 10);
if (end == text || *end != '\0' || value < 0 || value > 100) {
return false;
}
*out = (int) value;
return true;
}
int main(void)
{
char name[40];
if (!read_line("Enter name: ", name, sizeof name)) {
return EXIT_FAILURE;
}
int total = 0;
for (int i = 1; i <= 5; i++) {
char prompt[24], line[32];
snprintf(prompt, sizeof prompt, "Marks %d: ", i);
int marks;
for (;;) {
if (!read_line(prompt, line, sizeof line)) {
return EXIT_FAILURE;
}
if (parse_marks(line, &marks)) {
break;
}
printf("Please type a whole number from 0 to 100.\n");
}
total += marks;
}
printf("%s scored %d out of 500\n", name, total);
return 0;
}Enter name: Meera Nair Marks 1: 78 Marks 2: 85 Marks 3: 9O Please type a whole number from 0 to 100. Marks 3: 91 Marks 4: 66 Marks 5: 88 Meera Nair scored 408 out of 500
The text after each prompt is what the user typed. The typing mistake 9O (a capital O) is caught and asked again instead of silently producing garbage. The program is longer, but every line has a job, and it behaves the same on Windows, Linux and macOS. The techniques come from Input & Output and Command-line Arguments.
Older language features you will read at work
Company code written before C99 uses features that modern compilers reject or warn about:
/* C89 style: comments only like this, declarations at the top of each block */
#define TRUE 1
#define FALSE 0
typedef int BOOL;
static char *last_error; /* global state shared by everything */
count_items(list, n) /* K&R definition AND implicit int return type */
char **list;
int n;
{
register int i; /* 'register' is an ignored hint today */
int count;
count = 0;
for (i = 0; i < n; i++)
if (list[i] != NULL) count++;
return count;
}- Implicit
int: a missing type meantint. Removed in C99. - K&R parameter lists: types declared between the
)and the{, with no checking of calls. Removed in C23. - Implicit function declarations: calling a function with no prototype in scope. Removed in C99; the compiler guessed it returned
int, which silently broke functions returning pointers ordoubleon 64-bit systems. int f();used to mean "unknown parameters"; since C23 it means "no parameters", exactly likeint f(void).- Home-made booleans (
TRUE,FALSE,BOOL),NULLcomparisons everywhere, casts on everymalloc, and declarations only at the top of blocks. These are harmless but old-fashioned. - Non-reentrant library functions such as
strtokandasctime, which keep hidden internal state, and unboundedstrcpy,sprintfandatoi. These need care, as explained in Secure C Coding.
Building old code with a modern compiler
GCC 14 turned several of these old patterns from warnings into errors by default: implicit int, implicit function declarations, mixing integers and pointers (-Wint-conversion) and incompatible pointer types. A codebase that built for twenty years may suddenly fail:
gcc -c report.creport.c: In function 'print_report': report.c:14:11: error: implicit declaration of function 'strlen' [-Wimplicit-function-declaration]
The real fix is the one the compiler suggests: include <string.h>, add the prototype, or fix the types. As a temporary measure while you work through a large codebase, GCC offers -fpermissive, which turns these errors back into warnings, or an older standard such as -std=gnu89. Also, GCC 15 builds in C23 mode by default, where int f(); means "no parameters", so old code that calls such a function with arguments stops compiling; -std=gnu17 builds it while you add real prototypes.
A safe modernisation process:
- Build it exactly as it is, with the old standard if needed, and record the warnings.
- Write tests for the behaviour that matters before changing anything (Unit Testing in C).
- Fix one kind of problem at a time: prototypes, then types, then unsafe string functions. Run the tests after each step.
- Raise the standard (
-std=c17, then-std=c23) and the warning level step by step. - Do not rewrite working code just because it looks old. Change it when it is unsafe, wrong or in your way.
Common mistakes
- Copying textbook facts about sizes.
intis 4 bytes on modern PCs, not 2. Check withsizeof. - Using
fflush(stdin)to "clear the buffer". It is undefined in standard C; read whole lines instead. - Reaching for
-fpermissivepermanently. It hides real bugs such as wrong return types. - Big-bang rewrites. Rewriting a working legacy module from scratch without tests usually adds new bugs.
- Assuming Windows-only headers exist everywhere.
conio.h,dos.handgraphics.hare not C.
Key points
- Turbo C habits (
conio.h,clrscr,getch,void main,gets,fflush(stdin)) are non-standard, unsafe or both. - Turbo C's
intwas 16 bits; on modern systems it is 32 bits, so always usesizeofandstdint.h. - Old code may use implicit
int, K&R definitions and implicit function declarations, all removed from modern C. - GCC 14 reports several old patterns as errors; fix the code, using
-fpermissiveor an old-stdonly temporarily. - Modernise legacy code step by step, with tests, one kind of fix at a time.
Exercise
Modernise this function and its caller. Keep the behaviour: print each character of the string with its ASCII code, then return how many were uppercase letters.
#include <stdio.h>
#include <conio.h>
upper_count(s)
char *s;
{
int i, n;
n = 0;
for (i = 0; s[i]; i++) {
printf("%c=%d ", s[i], s[i]);
if (s[i] >= 'A' && s[i] <= 'Z') n++;
}
return n;
}
void main()
{
clrscr();
printf("\n%d\n", upper_count("ISRO"));
getch();
}Show solution
#include <ctype.h>
#include <stdio.h>
int upper_count(const char *s)
{
int n = 0;
for (size_t i = 0; s[i] != '\0'; i++) {
unsigned char c = (unsigned char) s[i];
printf("%c=%d ", c, c);
if (isupper(c)) {
n++;
}
}
return n;
}
int main(void)
{
printf("\n%d\n", upper_count("ISRO"));
return 0;
}I=73 S=83 R=82 O=79 4
The function now has a prototype-style definition with a const char * parameter and an explicit int return type, main returns int, and the Turbo C screen functions are gone. isupper from ctype.h replaces the hand-written range test and needs an unsigned char value.
Quick Quiz
1. Why is fflush(stdin) wrong in standard C?
Some compilers give it a meaning, but portable code reads whole lines instead.
2. How big was int in Turbo C, a 16-bit DOS compiler?
Turbo C targeted 16-bit DOS; modern PCs use a 4-byte int.
3. What did a missing return type mean in old C, as in count_items(list, n)?
Implicit int was removed in C99, and GCC 14 reports it as an error by default.
4. What is the best first step before modernising a large legacy module?
Tests let you change the code step by step and prove nothing broke.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
