EthicsComputerCourses
Join training +91 90532 10052

Legacy C at Work: Turbo C, gets & K&R Habits

C · Lesson 43 of 44 Advanced 8 min read Updated

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:

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 */
}
#HabitWhy it is a problemModern replacement
1conio.hA Borland/DOS header, not part of C; GCC on Linux does not have itRemove it; use only standard headers
2void main()Standard C requires int main(void) (or with argc, argv) for portable programs; the exit status is lostint main(void) and return 0;
3clrscr()Clears a DOS screen; does not exist elsewhereDo not clear the screen; plain output is easier to test
4gets(name)No size limit, so long input overflows name; removed in C11fgets(name, sizeof name, stdin)
5Unchecked scanfTyping letters leaves marks[i] unset and the loop misbehavesfgets plus strtol, with checks
6fflush(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
7getch()Kept the DOS window open; not standardRun 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

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;
}
Output
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:

C
/* 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 meant int. 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 or double on 64-bit systems.
  • int f(); used to mean "unknown parameters"; since C23 it means "no parameters", exactly like int f(void).
  • Home-made booleans (TRUE, FALSE, BOOL), NULL comparisons everywhere, casts on every malloc, and declarations only at the top of blocks. These are harmless but old-fashioned.
  • Non-reentrant library functions such as strtok and asctime, which keep hidden internal state, and unbounded strcpy, sprintf and atoi. 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:

Terminal
gcc -c report.c
Output
report.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:

  1. Build it exactly as it is, with the old standard if needed, and record the warnings.
  2. Write tests for the behaviour that matters before changing anything (Unit Testing in C).
  3. Fix one kind of problem at a time: prototypes, then types, then unsafe string functions. Run the tests after each step.
  4. Raise the standard (-std=c17, then -std=c23) and the warning level step by step.
  5. 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. int is 4 bytes on modern PCs, not 2. Check with sizeof.
  • Using fflush(stdin) to "clear the buffer". It is undefined in standard C; read whole lines instead.
  • Reaching for -fpermissive permanently. 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.h and graphics.h are 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 int was 16 bits; on modern systems it is 32 bits, so always use sizeof and stdint.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 -fpermissive or an old -std only 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.

C
#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
C
#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;
}
Output
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?

2. How big was int in Turbo C, a 16-bit DOS compiler?

3. What did a missing return type mean in old C, as in count_items(list, n)?

4. What is the best first step before modernising a large legacy module?

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