Common Stack Overflow Errors #3 C error handling

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


Comments

Leave a comment