Or rather the complete lack of it.
New c programmers coming from Python, Java, C#, JavaScript etc. expect to be told when something goes wrong. Exceptions get thrown, runtime engines complain, …
For example
f = open("demofile.txt", "r")
print(f.read())
On my machine (the file doesnt exist)
pm100@paul-think:~$ python3 so.py
Traceback (most recent call last):
File "so.py", line 1, in <module>
f = open("demofile.txt", "r")
FileNotFoundError: [Errno 2] No such file or directory: 'demofile.txt'
or
Console.WriteLine("Hello, World!");
System.IO.File.ReadAllLines("foo.txt");
produces
Hello, World!
Unhandled exception. System.IO.FileNotFoundException: Could not find file '/home/pm100/dotnetso/foo.txt'.
File name: '/home/pm100/dotnetso/foo.txt'
at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
at System.IO.File.InternalReadAllLines(String path, Encoding encoding)
at System.IO.File.ReadAllLines(String path)
at Program.<Main>$(String[] args) in /home/pm100/dotnetso/Program.cs:line 3
Aborted
Lets try the same thing with c
int main()
{
FILE* f1 = fopen("foo.txt", "r");
char buff[100];
fgets(buff, 100, f1);
}
and we get. On ubuntu
pm100@paul-think:~$ ./a.out
Segmentation fault
Windows debug build

Windows release build
PS C:\work\ConsoleApplication1\x64\Release> .\ConsoleApplication3.exe
PS C:\work\ConsoleApplication1\x64\Release>
with a very long pause before it terminates. A quick peek at the event log shows:
Fault bucket 1409284082708678959, type 5
Event Name: BEX64
Response: Not available
Cab Id: 0
Problem signature:
P1: ConsoleApplication3.exe
P2: 0.0.0.0
P3: 6410c09f
P4: ucrtbase.dll
P5: 10.0.22621.608
P6: f5fc15a3
P7: 000000000007df28
P8: c0000409
P9: 0000000000000005
P10:
Attached files:
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER.c6dbe521-0654-4447-8367-794a5e054b3a.tmp.dmp
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER.88649601-4bd5-4825-886f-02c896a8b0aa.tmp.WERInternalMetadata.xml
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER.36494a0d-6840-4814-9261-df06b7ced895.tmp.csv
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER.3621a395-06f2-4830-8241-187178fb2803.tmp.txt
\\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER.7341be0a-4b41-42c6-8b8f-0d20356c43b9.tmp.xml
These files may be available here:
\\?\C:\ProgramData\Microsoft\Windows\WER\ReportArchive\AppCrash_ConsoleApplicati_1a432a86f3872166d424532a337275b8040243a_91d25d0b_582f65fb-ad49-4bf4-b039-a03b18c263f6
Analysis symbol:
Rechecking for solution: 0
Report Id: 408c49a3-ff40-4f09-a151-9ba1fa8faad5
Report Status: 268435456
Hashed bucket: 10755fe50543261c938ec8681176892f
Cab Guid: 0
Ah yes, very helpful. Of course no novice dev would ever look in the event log anyway
Coders, check your returns
Every C function call (at least the ones in the standard libraries) return information to you indicating if they worked or not
It is the responsibility of the coder to check these
In addition the standard library calls will set a system variable called errno to indicate the reason. There are helper functions to convert the error tohuman readable text too.
Lets look at the man page for fopen (if all else fails, read the manual) fopen(3) — Linux manual page (man7.org)
Upon successful completion fopen(), fdopen(), and freopen()
return a FILE pointer. Otherwise, NULL is returned and errno is
set to indicate the error.
So now lets do
FILE* f1 = fopen("foo.txt", "r");
if (f1 == NULL) {
perror("Failed to open file");
exit(errno);
}
char buff[100];
fgets(buff, 100, f1);
perror makes a nice error message for us and the writes it to stderr
PS C:\work\ConsoleApplication1\x64\Release> .\ConsoleApplication3.exe
Failed to open file: No such file or directory
We also set the exit code to be the same value (just in case a shell script needs to test it).
Or you can check what error you got and do different tings with it
FILE* f1;
while (1) {
char fname[_MAX_PATH];
printf("enter file name:");
fgets(fname, sizeof(fname), stdin);
fname[strlen(fname) - 1] = 0;
f1 = fopen(fname, "r");
if (f1 == NULL) {
switch (errno) { // open failed .. why?
case EINVAL:
printf("invalid name\n");
continue;
case ENOENT:
printf("file not found\n");
continue;
}
perror("unknown error");
exit(errno);
}
else {
break; // file opened OK
}
}
char buff[100];
fgets(buff, 100, f1);
Note that you have to look at each call to see what it returns and what error it sets. For eaxmple the low level open function (open(2) — Linux manual page (man7.org) ) returns -1 on failure not NULL.
Check your bounds
Lets take this code for a spin
int main()
{
int nums[] = { 1,2,3,4,5 };
int k = 42;
for (int i = 0; i < 5; i++) {
nums[i + 1] = nums[i] + 2;
printf("%d", nums[i + 1]);
}
printf("%d", k);
}
VS2022 says

GCC on ubuntu says
pm100@paul-think:~$ ./a.out
3
5
7
9
11
42
pm100@paul-think:~$
Ie — it works fine
But GCC did complain
so2.c: In function ‘main’:
so2.c:15:19: warning: iteration 4 invokes undefined behavior [-Waggressive-loop-optimizations]
15 | nums[i+1] = nums[i] + 2;
| ~~~~~~~~~~^~~~~~~~~~~~~
so2.c:14:5: note: within this loop
14 | for(int i = 0; i < 5; i++){
| ^~~
but only when compiled with some optimization turned on.
What does python say
nums = [1,2,3,4,5]
for x in range(5):
nums[x+1] = nums[x] +2
print(nums[x + 1])
pm100@paul-think:~$ python3 so.py
3
5
7
9
Traceback (most recent call last):
File "so.py", line 3, in <module>
nums[x+1] = nums[x] +2
IndexError: list assignment index out of range
pm100@paul-think:~$
Python delivers the correct diagnosis.
Here is a demonstration of the worst of UB, the GCC version on ubuntu actually worked. But Bad Things ™ are happening. This code will eventually fail under some circumstances.
Conclusion
C puts you in charge. You must
- check return codes
- check bounds
There is no hand holding
Leave a comment