I used Linux Ubuntu 20.04 and gcc --version
9.4.0 on a 64-bit x86-64 computer, to write and test this answer.
How to get away from outdated Turbo C and install a free, modern OS and compiler
I recommend you go get any old used computer you can find, and install a modern operating system such as Ubuntu 22.04 LTS for free (go to https://ubuntu.com/ and click "Download" at the top; full installation instructions are here: https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview). This operating system is used by many modern tech companies world-wide, and will give you easy access to gcc, a modern compiler.
Ubuntu is a powerful platform and is quite user-friendly these days, with nice GUI interfaces, just like other modern OSs. As you grow, I encourage you to encourage your school and teachers to abandon Turbo C completely and migrate all instruction to Ubuntu which, again, is a free and open source and modern operating system capable of being used in a professional setting for real-life, modern code. It has been my primary OS on my work computers at both of my past tech companies I've worked for, for instance. I also use it at home on all of my computers, including for young kids. It runs the Chrome browser just fine, comes with LibreOffice text editors, Shotcut video editor software, built-in compilers and developer tools like gcc
(for C) and g++
(for C++), Python, Bash, Java, etc. I program Arduinos on it, design 3D models in https://www.tinkercad.com on it, and print to my 3D printer from it.
Build errors, and fixing the undefined behavior in scanf()
I tried to compile your program in gcc using these commands:
mkdir -p bin
gcc -Wall -Wextra -Werror -O3 -std=gnu17 test.c -o bin/a && bin/a
...and I got this error:
test.c:53:10: fatal error: conio.h: No such file or directory
53 | #include <conio.h>
| ^~~~~~~~~
compilation terminated.
I've never heard of conio.h
before, so I googled it and found this answer:
The conio.h header is specific to Turbo C, which predates the earliest C standard by several years. It contains routines that are specific to the DOS command line. One function here that's frequently used is getch
, which allows reading one character at a time without having to press the Enter key. It also contains gotoxy
which allows placing the cursor at a specific location in the terminal
Generally speaking, methods of communicating with the terminal like this are very OS specific, so each has their own (typically non-portable) way of doing it.
This contrasts with the functions in stdio.h which contain functions like printf
, scanf
, and getchar
which work regardless of what type of console is in use.
So, it does sound like the comment under the question which states you are using archaic and outdated tools is correct. Your school isn't using good, modern tools, and needs to change. Almost nobody uses DOS anymore. Tons of people and high-tech companies use Linux. Windows and Mac are even more popular. I recommend you switch to Linux. Use Turbo C just at school, but realize you are probably being taught a variety of outdated and bad practices, so research things online too and try to learn proper ways as you go, as well.
You can program on a 64-bit Linux machine for free online at https://www.onlinegdb.com/, but it's not the same as installing Ubuntu yourself.
Ok, so I cleaned up your code a bit, got rid of the unusual auto signed
usage, replaced your clear screen command with a system call that works on Linux, and now have this:
test.c:
#include <stdio.h>
#include <stdlib.h> // for `system()` call
int main(void)
{
int a;
// call the command-line command `clear` to clear the screen
int return_code = system("clear");
if (return_code != 0)
{
printf("Failed to clear screen.\n");
}
printf("enter character");
scanf("%c", &a);
switch(a)
{
case 'K':
printf("upper case");
break;
case 'k':
printf("lower case ");
break;
default:
printf("no match found");
break;
}
return 0;
}
With my build commands which produce better and safer output because I'm using -Wall -Wextra -Werror
, it won't build. I get the following errors. Here is the build command and errors:
$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 test.c -o bin/a && bin/a
test.c: In function ‘main’:
test.c:67:13: error: format ‘%c’ expects argument of type ‘char *’, but argument 2 has type ‘int *’ [-Werror=format=]
67 | scanf("%c", &a);
| ~^ ~~
| | |
| | int *
| char *
| %lc
test.c:67:5: error: ignoring return value of ‘scanf’, declared with attribute warn_unused_result [-Werror=unused-result]
67 | scanf("%c", &a);
| ^~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
So, to fix it, replace int a;
with char a;
and use the return value from scanf()
to check for errors. I now have this well-written, fully functional program:
#include <stdio.h>
#include <stdlib.h> // for `system()` call
int main(void)
{
char a;
// call the command-line command `clear` to clear the screen
int return_code = system("clear");
if (return_code != 0)
{
printf("Failed to clear screen.\n");
}
printf("Enter character: ");
int num_items_filled = scanf("%c", &a);
const int NUM_ITEMS_FILLED_EXPECTED = 1;
if (num_items_filled != NUM_ITEMS_FILLED_EXPECTED)
{
printf("Error: we expected %i item(s) to be filled, but got %i.\n",
NUM_ITEMS_FILLED_EXPECTED,
num_items_filled);
}
switch(a)
{
case 'K':
printf("Upper case.\n");
break;
case 'k':
printf("Lower case.\n");
break;
default:
printf("No match found.\n");
break;
}
return 0;
}
Example output:
Enter character: k
Lower case.
To use int a;
instead of char a;
is undefined behavior according to the C standard, which means it is a bug.
Going further: endianness detection, print all bytes in the int
and find the k
I upvoted @neilsen's answer. It is correct. Let's go a touch deeper and see what really happens when you use int a;
instead of char a;
. Note: this is undefined behavior, meaning the C standard does not define it, so it is a bug, and compiler and computer architecture-specific what I'm about to do. To force it to compile I will remove -Werror
from my build commands.
Here is my new build command:
gcc -Wall -Wextra -O3 -std=gnu17 test.c -o bin/a -lm && bin/a
Here is my new program. I removed the clear
call so we can still see the compiler output warning about our incorrect usage of scanf()
:
#include <stdio.h>
#include <stdlib.h> // for `system()` call
int main(void)
{
int a;
printf("Enter character: ");
int num_items_filled = scanf("%c", &a);
const int NUM_ITEMS_FILLED_EXPECTED = 1;
if (num_items_filled != NUM_ITEMS_FILLED_EXPECTED)
{
printf("Error: we expected %i item(s) to be filled, but got %i.\n",
NUM_ITEMS_FILLED_EXPECTED,
num_items_filled);
}
switch(a)
{
case 'K':
printf("Upper case.\n");
break;
case 'k':
printf("Lower case.\n");
break;
default:
printf("No match found.\n");
break;
}
return 0;
}
Now, here is a sample run output. Notice I get "No match found" now:
$ gcc -Wall -Wextra -O3 -std=gnu17 test.c -o bin/a -lm && bin/a
test.c: In function ‘main’:
test.c:60:36: warning: format ‘%c’ expects argument of type ‘char *’, but argument 2 has type ‘int *’ [-Wformat=]
60 | int num_items_filled = scanf("%c", &a);
| ~^ ~~
| | |
| | int *
| char *
| %lc
Enter character: k
No match found.
Let's see what bytes are inside int a
. New program: I added the print_bytes_in_variable()
function and some stuff after the switch
statement, including endianness detection:
#include <stdint.h> // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>
#include <stdlib.h> // for `system()` call
void print_bytes_in_variable(uint8_t* byte_array, size_t num_bytes)
{
printf("Bytes are: ");
for (size_t i = 0; i < num_bytes; i++)
{
printf("0x%02x ", byte_array[i]);
}
printf("\n");
}
int main(void)
{
int a;
printf("Enter character: ");
int num_items_filled = scanf("%c", &a);
const int NUM_ITEMS_FILLED_EXPECTED = 1;
if (num_items_filled != NUM_ITEMS_FILLED_EXPECTED)
{
printf("Error: we expected %i item(s) to be filled, but got %i.\n",
NUM_ITEMS_FILLED_EXPECTED,
num_items_filled);
}
switch(a)
{
case 'K':
printf("Upper case.\n");
break;
case 'k':
printf("Lower case.\n");
break;
default:
printf("No match found.\n");
break;
}
// prove we know how to read bytes by reading them one-at-a-time from an int
int b = 0x04030201;
uint8_t* byte_array = (uint8_t*)&b;
printf("\n");
print_bytes_in_variable(byte_array, sizeof(b));
// indicate endianness;
// read about it here: https://en.wikipedia.org/wiki/Endianness
if (byte_array[0] == 0x01) // this check works on any architecture
{
printf("My system is **little-endian**, since the least-significant\n"
"(smallest-value) byte is first.\n");
}
// this check works only on architectures with 4-byte or larger
// `int`s since I wrote the `0x04` into the 4th byte in `int b`.
else if (byte_array[0] == 0x04)
{
printf("My system is **big-endian**, since the most-significant\n"
"(biggest-value) byte is first.\n");
}
// Now check the values of each byte in the `int a`
printf("\nInside `a`:\n");
print_bytes_in_variable((uint8_t*)&a, sizeof(a));
return 0;
}
Sample run and output:
$ gcc -Wall -Wextra -O3 -std=gnu17 test.c -o bin/a -lm && bin/a
test.c: In function ‘main’:
test.c:71:36: warning: format ‘%c’ expects argument of type ‘char *’, but argument 2 has type ‘int *’ [-Wformat=]
71 | int num_items_filled = scanf("%c", &a);
| ~^ ~~
| | |
| | int *
| char *
| %lc
Enter character: k
No match found.
Bytes are: 0x01 0x02 0x03 0x04
My system is **little-endian**, since the least-significant
(smallest-value) byte is first.
Inside `a`:
Bytes are: 0x6b 0x43 0xc8 0x61
And, there is your k
! The ASCII tables indicate a k
is hex 6B
, and you can see that in the first (least-significant in my case) byte printed just above! The other values after that, however, are garbage (undefined) values that were sitting in my memory prior to running this program.
To simply write them all to 0s to fix this, change int a;
to int a = 0;
, and now your program works! Run it again and you'll see:
Enter character: k
Lower case.
So, it works, yes, but the compiler warning is still there, and the program still relies on undefined behavior in scanf()
, so it is still buggy and broken even though it works as expected. Weird, huh? Well, that's how it is. We are relying on predictable (for my compiler and computer hardware) but undefined behavior (per the C standard), to get my program to "work". It is broken though. Fix the compiler warning. Add -Werror
back to turn warnings into errors, and use char a;
instead of int a;
to fix the undefined behavior in scanf()
.
The end.
References
- https://en.wikipedia.org/wiki/ASCII#Printable_characters
- https://en.wikipedia.org/wiki/Endianness
- https://cplusplus.com/reference/cstdio/scanf/
- https://cplusplus.com/reference/cstdlib/system/
- https://en.cppreference.com/w/c/program/system
- Why use conio.h?
- https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview
- https://en.wikipedia.org/wiki/Borland_Turbo_C