Forty Slots, Forty-Five Students
Bikash, a Grade 12 student in Birgunj, writes a C program for his project. It should read the number of students, read each student's name and marks, print the class average to two decimal places, and list every student who scored above average. His declarations are char names[40][20]; int marks[40]; int n, i, sum = 0; float avg; and the loop body is scanf("%s", names[i]); scanf("%d", &marks[i]); sum += marks[i];. After the loop he writes avg = sum/n; and tests for a particular student with if (names[i] == "Sita"). His class has 45 students. When he runs it with 45, the last few names print as garbage and the program sometimes crashes. When he enters the name 'Ramesh Shrestha', the marks prompt is skipped for the next student and the input runs away. With correct marks totalling 2781 over 45 students it prints an average of 61, not 61.80. The Sita test never prints, and entering 0 as the class size crashes it outright. His friend ran the same code with 30 students and says it works fine.
1. Identify each defect in the program, name the class of error it belongs to, and state the one-line or one-clause fix for each.
Read the worked answer
- Array bound overrun.
names[40][20]andmarks[40]cannot hold 45 students. Writingnames[40]tonames[44]writes past the end of the array - undefined behaviour, not a defined error. Fix: size the arrays to a named maximum (#define MAX 60) and reject anyn > MAXbefore the loop, or allocate withmalloc(n * sizeof *marks). %sstops at whitespace.scanf("%s", names[i])stores only 'Ramesh' and leaves ' Shrestha\n' in the input buffer. The nextscanf("%d")sees 'S', fails, and returns 0 without consuming anything;marks[i]keeps an uninitialised value and the following iterations keep hitting the same leftover text, which is why the prompts appear to be skipped. Fix:fgets(names[i], sizeof names[i], stdin)and strip the newline, orscanf(" %[^\n]", names[i])with a width limit.- Integer division.
sum/ndivides int by int and truncates: 2781/45 gives 61, and only then is the result converted to float. Fix:avg = (float) sum / n;. - Pointer comparison instead of string comparison.
names[i] == "Sita"compares two addresses, which are never equal. Fix:strcmp(names[i], "Sita") == 0with#include <string.h>. - Division by zero.
n = 0makessum/nundefined; on most systems it raises a floating-point exception and aborts. Fix: validatenafter reading it.
All five are logic or memory errors the compiler does not report, which is why the program builds cleanly and still fails.
2. Rewrite the input loop and the average calculation so that all the defects are removed, keeping the program's original behaviour otherwise.
Read the worked answer
#include <stdio.h>
#include <string.h>
#define MAX 60
#define NAMELEN 40
int main(void) {
char names[MAX][NAMELEN];
int marks[MAX];
int n, i, sum = 0;
float avg;
printf("Number of students: ");
if (scanf("%d", &n) != 1 || n <= 0 || n > MAX) {
printf("Invalid class size\n");
return 1;
}
getchar(); /* consume the newline after n */
for (i = 0; i < n; i++) {
printf("Name of student %d: ", i + 1);
if (fgets(names[i], NAMELEN, stdin) == NULL) return 1;
names[i][strcspn(names[i], "\n")] = '\0';
printf("Marks: ");
if (scanf("%d", &marks[i]) != 1) {
printf("Bad marks\n");
return 1;
}
getchar();
sum += marks[i];
}
avg = (float) sum / n;
printf("Average = %.2f\n", avg);
for (i = 0; i < n; i++)
if (marks[i] > avg)
printf("%s\n", names[i]);
return 0;
}
Why each change was made, in order of importance: the bound check on n removes the overrun and the divide-by-zero in one guard; fgets reads the whole line including the space in 'Ramesh Shrestha' and cannot overflow because it is given the buffer size; strcspn strips the newline that fgets keeps, otherwise the printed names carry a blank line; getchar() after scanf("%d") throws away the newline that would otherwise be read as an empty name; checking the return value of every scanf means bad input is reported rather than silently propagated; and the cast before division forces floating-point division. %.2f then prints 61.80.
3. Bikash's friend ran the identical code with 30 students and reports that it works perfectly. Explain how a program with an out-of-bounds write can appear to work, and why 'it worked on my machine' is not evidence that the code is correct.
Read the worked answer
Writing past the end of an array is undefined behaviour: the C standard says nothing about what must happen, so the compiler is free to produce code that appears to work. In practice names and marks are local variables on the stack. Beyond them lie other locals, saved registers, padding, and finally the return address. Writing 5 elements past the end of an array of 40 overwrites whatever happens to be there.
With 30 students there is no write past the end at all - the loop stays inside the declared 40 - so the program is behaving within its bounds and the output is genuinely correct for that run. That is not a lucky break, it is simply the case that never exercises the bug. The same source file compiled with different optimisation settings, a different compiler, or run on a system with a different stack layout will overwrite the return address instead of padding, and main will return to a garbage address - the classic 'sometimes crashes' symptom.
Three lessons worth stating in an exam answer. First, undefined behaviour can be silent; the absence of a crash proves nothing. Second, testing with one input size only tests that input size - boundary values (0, 1, 40, 41, 45) must be tested deliberately. Third, tools exist for exactly this: compiling with -Wall -Wextra and running under AddressSanitizer or valgrind would have flagged the overflow on the very first 45-student run, before any crash made it visible.