А точнее полное его отсутствие.

Новые программисты на C, пришедшие из Python, Java, C#, JavaScript и т. д., ожидают, что им сообщат, когда что-то пойдет не так. Генерируются исключения, исполняющие механизмы жалуются...

Например

f = open("demofile.txt", "r")
print(f.read())

На моей машине (файл не существует)

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");

производит

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

Давайте попробуем то же самое с c

int main()
{
    FILE* f1 = fopen("foo.txt", "r");
    char buff[100];
    fgets(buff, 100, f1); 

}

и мы получаем. На убунту

pm100@paul-think:~$ ./a.out
Segmentation fault

Отладочная сборка Windows

Сборка релиза Windows

PS C:\work\ConsoleApplication1\x64\Release> .\ConsoleApplication3.exe
PS C:\work\ConsoleApplication1\x64\Release>

с очень длинной паузой перед прекращением. Быстрый взгляд на журнал событий показывает:

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

Ах да, очень полезно. Конечно, ни один начинающий разработчик никогда не заглянет в журнал событий.

Кодеры, проверьте свои возвраты

Каждый вызов функции C (по крайней мере, в стандартных библиотеках) возвращает вам информацию, указывающую, сработали они или нет.

Кодер отвечает за их проверку

Кроме того, вызовы стандартной библиотеки будут устанавливать системную переменную с именем errno для указания причины. Существуют также вспомогательные функции для преобразования ошибки в удобочитаемый текст.

Давайте посмотрим на справочную страницу для fopen (если ничего не помогает, прочтите руководство) fopen(3) — справочная страница Linux (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.

Итак, теперь давайте

    FILE* f1 = fopen("foo.txt", "r");
    if (f1 == NULL) {
        perror("Failed to open file");
        exit(errno);
    }
    char buff[100];
    fgets(buff, 100, f1);

perror делает для нас красивое сообщение об ошибке и записывает его в stderr

PS C:\work\ConsoleApplication1\x64\Release> .\ConsoleApplication3.exe
Failed to open file: No such file or directory

Мы также устанавливаем код выхода равным тому же значению (на всякий случай, если сценарий оболочки должен его протестировать).

Или вы можете проверить, какую ошибку вы получили, и сделать с ней разные вещи.

    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);

Обратите внимание, что вам нужно просмотреть каждый вызов, чтобы увидеть, что он возвращает и какую ошибку устанавливает. Например, низкоуровневая функция open (open(2) — справочная страница Linux (man7.org)) возвращает -1 в случае ошибки, а не NULL.

Проверьте свои границы

Давайте возьмем этот код для вращения

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 говорит

GCC на Ubuntu говорит

pm100@paul-think:~$ ./a.out
3
5
7
9
11
42
pm100@paul-think:~$

Т.е. — работает нормально

Но GCC жаловался

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++){
      |     ^~~

но только при компиляции с включенной оптимизацией.

Что говорит питон

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 ставит правильный диагноз.

Вот демонстрация худшего из UB, версия GCC на ubuntu реально работала. Но Плохие Вещи (тм) случаются. Этот код, в конечном счете, потерпит неудачу при некоторых обстоятельствах.

Заключение

C возлагает на вас ответственность. Вы должны

  • проверить коды возврата
  • проверить границы

Нет руки