А точнее полное его отсутствие.
Новые программисты на 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 возлагает на вас ответственность. Вы должны
- проверить коды возврата
- проверить границы
Нет руки