Введение
Ошибки, увы, неизбежны, поэтому их обработка занимает очень важное место в программировании. И если алгоритмические ошибки можно выявить и исправить во время написания и тестирования программы, то ошибок времени выполнения избежать нельзя в принципе. Сегодня мы рассмотрим функции стандартной библиотеки (C Standard Library) и POSIX, используемые в обработке ошибок.
Переменная errno и коды ошибок
<errno.h>
errno – переменная, хранящая целочисленный код последней ошибки. В каждом потоке существует своя локальная версия errno, чем и обусловливается её безопасность в многопоточной среде. Обычно errno реализуется в виде макроса, разворачивающегося в вызов функции, возвращающей указатель на целочисленный буфер. При запуске программы значение errno равно нулю.
Все коды ошибок имеют положительные значения, и могут использоваться в директивах препроцессора #if. В целях удобства и переносимости заголовочный файл
<errno.h>
определяет макросы, соответствующие кодам ошибок. Стандарт ISO C определяет следующие коды:
- EDOM – (Error domain) ошибка области определения.
- EILSEQ – (Error invalid sequence) ошибочная последовательность байтов.
- ERANGE – (Error range) результат слишком велик.
Прочие коды ошибок (несколько десятков) и их описания определены в стандарте POSIX. Кроме того, в спецификациях стандартных функций обычно указываются используемые ими коды ошибок и их описания.
Нехитрый скрипт печатает в консоль коды ошибок, их символические имена и описания:
#!/usr/bin/perl
use strict;
use warnings;
use Errno;
foreach my $err (sort keys (%!)) {
$! = eval "Errno::$err";
printf "%20s %4d %s\n", $err, $! + 0, $!
}
Если вызов функции завершился ошибкой, то она устанавливает переменную errno в ненулевое значение. Если же вызов прошёл успешно, функция обычно не проверяет и не меняет переменную errno. Поэтому перед вызовом функции её нужно установить в
0
.Пример:
/* convert from UTF16 to UTF8 */
errno = 0;
n_ret = iconv(icd, (char **) &p_src, &n_src, &p_dst, &n_dst);
if (n_ret == (size_t) -1) {
VJ_PERROR();
if (errno == E2BIG)
fprintf(stderr, " Error : input conversion stopped due to lack of space in the output buffer\n");
else if (errno == EILSEQ)
fprintf(stderr, " Error : input conversion stopped due to an input byte that does not belong to the input codeset\n");
else if (errno == EINVAL)
fprintf(stderr, " Error : input conversion stopped due to an incomplete character or shift sequence at the end of the input buffer\n");
/* clean the memory */
free(p_out_buf);
errno = 0;
n_ret = iconv_close(icd);
if (n_ret == (size_t) -1)
VJ_PERROR();
return (size_t) -1;
}
Как видите, описания ошибок в спецификации функции
iconv()
более информативны, чем в <errno.h>
. Функции работы с errno
Получив код ошибки, хочется сразу получить по нему её описание. К счастью, ISO C предлагает целый набор полезных функций.
<stdio.h>
void perror(const char *s);
Печатает в stderr содержимое строки
s
, за которой следует двоеточие, пробел и сообщение об ошибке. После чего печатает символ новой строки '\n'
. Пример:
/*
// main.c
// perror example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
if (file) {
// Do something useful.
fclose(file);
}
else {
perror("fopen() ");
}
return EXIT_SUCCESS;
}
<string.h>
char* strerror(int errnum);
Возвращает строку, содержащую описание ошибки
errnum
. Язык сообщения зависит от локали (немецкий, иврит и даже японский), но обычно поддерживается лишь английский. /*
// main.c
// strerror example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
char *errorbuf = strerror(error_num);
fprintf(stderr, "Error message : %s\n", errorbuf);
}
return EXIT_SUCCESS;
}
strerror()
не безопасная функция. Во-первых, возвращаемая ею строка не является константной. При этом она может храниться в статической или в динамической памяти в зависимости от реализации. В первом случае её изменение приведёт к ошибке времени выполнения. Во-вторых, если вы решите сохранить указатель на строку, и после вызовите функцию с новым кодом, все прежние указатели будут указывать уже на новую строку, ибо она использует один буфер для всех строк. В-третьих, её поведение в многопоточной среде не определено в стандарте. Впрочем, в QNX она объявлена как thread safe. Поэтому в новом стандарте ISO C11 были предложены две очень полезные функции.
size_t strerrorlen_s(errno_t errnum);
Возвращает длину строки с описанием ошибки
errnum
. errno_t strerror_s(char *buf, rsize_t buflen, errno_t errnum);
Копирует строку с описание ошибки
errnum
в буфер buf
длиной buflen
. Пример:
/*
// main.c
// strerror_s example
//
// Created by Ariel Feinerman on 23/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
#ifdef __STDC_LIB_EXT1__
size_t error_len = strerrorlen_s(errno) + 1;
char error_buf[error_len];
strerror_s(error_buf, error_len, errno);
fprintf(stderr, "Error message : %s\n", error_buf);
#endif
}
return EXIT_SUCCESS;
}
Функции входят в Annex K (Bounds-checking interfaces), вызвавший много споров. Он не обязателен к выполнению и целиком не реализован ни в одной из свободных библиотек. Open Watcom C/C++ (Windows), Slibc (GNU libc) и Safe C Library (POSIX), в последней, к сожалению, именно эти две функции не реализованы. Тем не менее, их можно найти в коммерческих средах разработки и системах реального времени, Embarcadero RAD Studio, INtime RTOS, QNX.
Стандарт POSIX.1-2008 определяет следующие функции:
char *strerror_l(int errnum, locale_t locale);
Возвращает строку, содержащую локализованное описание ошибки
errnum
, используя locale
. Безопасна в многопоточной среде. Не реализована в Mac OS X, FreeBSD, NetBSD, OpenBSD, Solaris и прочих коммерческих UNIX. Реализована в Linux, MINIX 3 и Illumos (OpenSolaris).Пример:
/*
// main.c
// strerror_l example – works on Linux, MINIX 3, Illumos
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
int main(int argc, const char * argv[])
{
locale_t locale = newlocale(LC_ALL_MASK, "fr_FR.UTF-8", (locale_t) 0);
if (!locale) {
fprintf(stderr, "Error: cannot create locale.");
exit(EXIT_FAILURE);
}
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(tmpnam(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
char *error_buf = strerror_l(errno, locale);
fprintf(stderr, "Error message : %s\n", error_buf);
}
freelocale(locale);
return EXIT_SUCCESS;
}
Вывод:
Error message : Aucun fichier ou dossier de ce type
int strerror_r(int errnum, char *buf, size_t buflen);
Копирует строку с описание ошибки
errnum
в буфер buf
длиной buflen
. Если buflen
меньше длины строки, лишнее обрезается. Безопасна в многоготочной среде. Реализована во всех UNIX.Пример:
/*
// main.c
// strerror_r POSIX example
//
// Created by Ariel Feinerman on 25/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MSG_LEN 1024
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
char error_buf[MSG_LEN];
errno_t error = strerror_r (error_num, error_buf, MSG_LEN);
switch (error) {
case EINVAL:
fprintf (stderr, "strerror_r() failed: invalid error code, %d\n", error);
break;
case ERANGE:
fprintf (stderr, "strerror_r() failed: buffer too small: %d\n", MSG_LEN);
case 0:
fprintf(stderr, "Error message : %s\n", error_buf);
break;
default:
fprintf (stderr, "strerror_r() failed: unknown error, %d\n", error);
break;
}
}
return EXIT_SUCCESS;
}
Увы, никакого аналога
strerrorlen_s()
в POSIX не определили, поэтому длину строки можно выяснить лишь экспериментальным путём. Обычно 300 символов хватает за глаза. GNU C Library в реализации strerror()
использует буфер длиной в 1024 символа. Но мало ли, а вдруг?Пример:
/*
// main.c
// strerror_r safe POSIX example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MSG_LEN 1024
#define MUL_FACTOR 2
int main(int argc, const char * argv[])
{
// Generate unique filename.
char *file_name = tmpnam((char[L_tmpnam]){0});
errno = 0;
FILE *file = fopen(file_name, "rb");
// Save error number.
errno_t error_num = errno;
if (file) {
// Do something useful.
fclose(file);
}
else {
errno_t error = 0;
size_t error_len = MSG_LEN;
do {
char error_buf[error_len];
error = strerror_r (error_num, error_buf, error_len);
switch (error) {
case 0:
fprintf(stderr, "File : %s\nLine : %d\nCurrent function : %s()\nFailed function : %s()\nError message : %s\n", __FILE__, __LINE__, __func__, "fopen", error_buf);
break;
case ERANGE:
error_len *= MUL_FACTOR;
break;
case EINVAL:
fprintf (stderr, "strerror_r() failed: invalid error code, %d\n", error_num);
break;
default:
fprintf (stderr, "strerror_r() failed: unknown error, %d\n", error);
break;
}
} while (error == ERANGE);
}
return EXIT_SUCCESS;
}
Вывод:
File : /Users/ariel/main.c
Line : 47
Current function : main()
Failed function : fopen()
Error message : No such file or directory
Макрос assert()
<assert.h>
void assert(expression)
Макрос, проверяющий условие
expression
(его результат должен быть числом) во время выполнения. Если условие не выполняется (expression
равно нулю), он печатает в stderr значения __FILE__
, __LINE__
, __func__
и expression
в виде строки, после чего вызывает функцию abort()
. /*
// main.c
// assert example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
int main(int argc, const char * argv[]) {
double x = -1.0;
assert(x >= 0.0);
printf("sqrt(x) = %f\n", sqrt(x));
return EXIT_SUCCESS;
}
Вывод:
Assertion failed: (x >= 0.0), function main, file /Users/ariel/main.c, line 17.
Если макрос
NDEBUG
определён перед включением <assert.h>
, то assert()
разворачивается в ((void) 0)
и не делает ничего. Используется в отладочных целях. Пример:
/*
// main.c
// assert_example
//
// Created by Ariel Feinerman on 23/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#NDEBUG
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
int main(int argc, const char * argv[]) {
double x = -1.0;
assert(x >= 0.0);
printf("sqrt(x) = %f\n", sqrt(x));
return EXIT_SUCCESS;
}
Вывод:
sqrt(x) = nan
Функции atexit(), exit() и abort()
<stdlib.h>
int atexit(void (*func)(void));
Регистрирует функции, вызываемые при нормальном завершении работы программы в порядке, обратном их регистрации. Можно зарегистрировать до 32 функций.
_Noreturn void exit(int exit_code);
Вызывает нормальное завершение программы, возвращает в среду число
exit_code
. ISO C стандарт определяет всего три возможных значения: 0
, EXIT_SUCCESS
и EXIT_FAILURE
. При этом вызываются функции, зарегистрированные через atexit()
, сбрасываются и закрываются потоки ввода — вывода, уничтожаются временные файлы, после чего управление передаётся в среду. Функция exit()
вызывается в main()
при выполнении return или достижении конца программы. Главное преимущество
exit()
в том, что она позволяет завершить программу не только из main()
, но и из любой вложенной функции. К примеру, если в глубоко вложенной функции выполнилось (или не выполнилось) некоторое условие, после чего дальнейшее выполнение программы теряет всякий смысл. Подобный приём (early exit) широко используется при написании демонов, системных утилит и парсеров. В интерактивных программах с бесконечным главным циклом exit()
можно использовать для выхода из программы при выборе нужного пункта меню. Пример:
/*
// main.c
// exit example
//
// Created by Ariel Feinerman on 17/03/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void third_2(void)
{
printf("third #2\n"); // Does not print.
}
void third_1(void)
{
printf("third #1\n"); // Does not print.
}
void second(double num)
{
printf("second : before exit()\n"); // Prints.
if ((num < 1.0f) && (num > -1.0f)) {
printf("asin(%.1f) = %.3f\n", num, asin(num));
exit(EXIT_SUCCESS);
}
else {
fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]\n", num);
exit(EXIT_FAILURE);
}
printf("second : after exit()\n"); // Does not print.
}
void first(double num)
{
printf("first : before second()\n")
second(num);
printf("first : after second()\n"); // Does not print.
}
int main(int argc, const char * argv[])
{
atexit(third_1); // Register first handler.
atexit(third_2); // Register second handler.
first(-3.0f);
return EXIT_SUCCESS;
}
Вывод:
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
third #2
third #1
_Noreturn void abort(void);
Вызывает аварийное завершение программы, если сигнал не был перехвачен обработчиком сигналов. Временные файлы не уничтожаются, закрытие потоков определяется реализацией. Самое главное отличие вызовов abort() и
exit(EXIT_FAILURE)
в том, что первый посылает программе сигнал SIGABRT
, его можно перехватить и произвести нужные действия перед завершением программы. Записывается дамп памяти программы (core dump file), если они разрешены. При запуске в отладчике он перехватывает сигнал SIGABRT
и останавливает выполнение программы, что очень удобно в отладке. Пример:
/*
// main.c
// abort example
//
// Created by Ariel Feinerman on 17/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void third_2(void)
{
printf("third #2\n"); // Does not print.
}
void third_1(void)
{
printf("third #1\n"); // Does not print.
}
void second(double num)
{
printf("second : before exit()\n"); // Prints.
if ((num < 1.0f) && (num > -1.0f)) {
printf("asin(%.1f) = %.3f\n", num, asin(num));
exit(EXIT_SUCCESS);
}
else {
fprintf(stderr, "Error: %.1f is beyond the range [-1.0; 1.0]\n", num);
abort();
}
printf("second : after exit()\n"); // Does not print.
}
void first(double num)
{
printf("first : before second()\n");
second(num);
printf("first : after second()\n"); // Does not print.
}
int main(int argc, const char * argv[])
{
atexit(third_1); // register first handler
atexit(third_2); // register second handler
first(-3.0f);
return EXIT_SUCCESS;
}
Вывод:
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
Abort trap: 6
Вывод в отладчике:
$ lldb abort_example
(lldb) target create "abort_example"
Current executable set to 'abort_example' (x86_64).
(lldb) run
Process 22570 launched: '/Users/ariel/abort_example' (x86_64)
first : before second()
second : before exit()
Error: -3.0 is beyond the range [-1.0; 1.0]
Process 22570 stopped
* thread #1: tid = 0x113a8, 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff89c01286 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
-> 0x7fff89c01286 <+10>: jae 0x7fff89c01290 ; <+20>
0x7fff89c01288 <+12>: movq %rax, %rdi
0x7fff89c0128b <+15>: jmp 0x7fff89bfcc53 ; cerror_nocancel
0x7fff89c01290 <+20>: retq
(lldb)
В случае критической ошибки нужно использовать функцию
abort()
. К примеру, если при выделении памяти или записи файла произошла ошибка. Любые дальнейшие действия могут усугубить ситуацию. Если завершить выполнение обычным способом, при котором производится сброс потоков ввода — вывода, можно потерять ещё неповрежденные данные и временные файлы, поэтому самым лучшим решением будет записать дамп и мгновенно завершить программу. В случае же некритической ошибки, например, вы не смогли открыть файл, можно безопасно выйти через
exit()
.Функции setjmp() и longjmp()
Вот мы и подошли к самому интересному – функциям нелокальных переходов.
setjmp()
и longjmp()
работают по принципу goto, но в отличие от него позволяют перепрыгивать из одного места в другое в пределах всей программы, а не одной функции. <setjmp.h>
int setjmp(jmp_buf env);
Сохраняет информацию о контексте выполнения программы (регистры микропроцессора и прочее) в
env
. Возвращает 0
, если была вызвана напрямую или value
, если из longjmp()
. void longjmp(jmp_buf env, int value);
Восстанавливает контекст выполнения программы из
env
, возвращает управление setjmp()
и передаёт ей value
.Пример:
/*
// main.c
// setjmp simple
//
// Created by Ariel Feinerman on 18/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
static jmp_buf buf;
void second(void)
{
printf("second : before longjmp()\n"); // prints
longjmp(buf, 1); // jumps back to where setjmp was called – making setjmp now return 1
printf("second : after longjmp()\n"); // does not prints
// <- Here is the point that is never reached. All impossible cases like your own house in Miami, your million dollars, your nice girl, etc.
}
void first(void)
{
printf("first : before second()\n");
second();
printf("first : after second()\n"); // does not print
}
int main(int argc, const char * argv[])
{
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("main\n"); // prints
return EXIT_SUCCESS;
}
Вывод:
first : before second()
second : before longjmp()
main
Используя
setjmp()
и longjmp
() можно реализовать механизм исключений. Во многих языках высокого уровня (например, в Perl) исключения реализованы через них. Пример:
/*
// main.c
// exception simple
//
// Created by Ariel Feinerman on 18/02/17.
// Copyright 2017 Feinerman Research, Inc. All rights reserved.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <setjmp.h>
#define str(s) #s
static jmp_buf buf;
typedef enum {
NO_EXCEPTION = 0,
RANGE_EXCEPTION = 1,
NUM_EXCEPTIONS
} exception_t;
static char *exception_name[NUM_EXCEPTIONS] = {
str(NO_EXCEPTION),
str(RANGE_EXCEPTION)
};
float asin_e(float num)
{
if ((num < 1.0f) && (num > -1.0f)) {
return asinf(num);
}
else {
longjmp(buf, RANGE_EXCEPTION); // | @throw
}
}
void do_work(float num)
{
float res = asin_e(num);
printf("asin(%f) = %f\n", num, res);
}
int main(int argc, const char * argv[])
{
exception_t exc = NO_EXCEPTION;
if (!(exc = setjmp(buf))) { // |
do_work(-3.0f); // | @try
} // |
else { // |
fprintf(stderr, "%s was hadled in %s()\n", exception_name[exc], __func__); // | @catch
} // |
return EXIT_SUCCESS;
}
Вывод:
RANGE_EXCEPTION was hadled in main()
Внимание! Функции
setjmp()
и longjmp
() в первую очередь применяются в системном программировании, и их использование в клиентском коде не рекомендуется. Их применение ухудшает читаемость программы и может привести к непредсказуемым ошибкам. Например, что произойдёт, если вы прыгните не вверх по стеку – в вызывающую функцию, а в параллельную, уже завершившую выполнение? Информация
- стандарт ISO/IEC C (89/99/11)
- Single UNIX Specifcation, Version 4, 2016 Edition
- The Open Group Base Specifcations Issue 7, 2016 Edition (POSIX.1-2008)
- SEI CERT C Coding Standard
- cправочная информация среды программирования
- справочная информация операционной системы (man pages)
- заголовочные файлы (/usr/include)
- исходные тексты библиотеки (C Standard Library)
Комментарии (41)
hdfan2
03.04.2017 09:34+2Добавлю: у замечательной функции getaddrinfo свои собственные коды ошибок, и для их перевода в человекочитаемую форму есть отдельная функция gai_strerror. Как-то раз на это налетел, поэтому запомнилось.
kloppspb
03.04.2017 14:20Их применение ухудшает читаемость программы и может привести к непредсказуемым ошибкам. Например, что произойдёт, если вы прыгните не вниз по стеку – в функцию верхнего уровня, а в параллельную, уже завершившую выполнение?
Этот вопрос решается обёртками, которых можно какое-то количество, например, на гитхабе. С их применением код на C может выглядеть так (с контролем мест прыжков и вложенности):
try { throw( IOException, "Фигня какая-то случилась" ); } catch( IOException ) { } catch() { } finally { }
Другой вопрос — так уж ли это надо в C.arielf
03.04.2017 14:59Другой вопрос — так уж ли это надо в C.
Я считаю, что в клиентском коде (обычной юзерской проге) не нужно. Я умеренный противник исключений, ибо они прерывают обычный поток выполнения, что противоречит принципу структурного программирования. Но в системном программировании они могут быть очень полезны. Приведённый Вами пример хорош, пока у вас throw прямо в блоке try, а если оно во вложенном вызове функции и в другом файле — придётся сильнее изловчиться.kloppspb
03.04.2017 15:28если оно во вложенном вызове функции и в другом файле — придётся сильнее изловчиться
Да не такая уж это и проблема.
Но C в этом смысле вообще консервативен. Немало подобных обёрток и библиотек делается скорей из спортивного интереса, чем из реальной необходимости. Какое-то время ими даже пользуются (в основном авторы, и я не избежал этой участи), но потом забивают и возвращаются к классике :)arielf
03.04.2017 18:35Можно ссылку на репу? Интересно глянуть. Вообще проблема, ибо
longjmp()
не умеет прыгать 'в никуда', ей нужен конкретный буфер с сохранённой средой. Значит, эти буферы в общем случае должны быть глобальными и уникальными. И их нужно генерить неким автоматическим способом (препроцессором?). Я ещё не изучал существующие реализации, мож попробую напишу свою.kloppspb
03.04.2017 20:32+1Идею брал отсюда. Есть ещё несколько реализаций, но они все похожи.
В своей делал тупо: массив структур {jmp_buf + тип исключения + строка для сообщения}, и глобальный индекс текущей позиции. Соответственно, блок try заполняет текущую структуру и переставляет указатель на следующую. Блок finally() откатывает указатель, то есть обязателен в любом случае. Конечно, тут должна сохраняться последовательность обработки. В принципе тоже преодолимо, но наигрался уже на этом этапе :)
exchg
03.04.2017 21:39Значит, эти буферы в общем случае должны быть глобальными и уникальными. И их нужно генерить неким автоматическим способом (препроцессором?).
Их можно хранить в стеке вызывающей функции + глобальный указатель на предыдущий буфер.
exchg
03.04.2017 21:46Точнее, в стеке хранится текущий буфер и указатель на предыдущий буфер, а в в глобальной переменной хранится указатель на текущий буфер.
exchg
03.04.2017 18:27+2Наверное стоит добавить что в случае использования sjlj стоит следить за переменными (например маркировать их volatile) потому что longjmp восстанавливает контекст сохраненный в момент вызова setjmp, т.е
void* data = allocate_some(); if (!(exc = setjmp(buf))) { data = reallocate_some(); do_work(-3.0f); } else { // тут значение data МОЖЕТ быть равное значению сохраненному на момент первого вызовы setjmp fprintf(stderr, "%s was hadled in %s()\n", exception_name[exc], __func__); }
arielf
03.04.2017 18:38Есть такое, с этими функциями вообще много проблем, но пример был игрушечным, в реальном коде лучше вообще их не применять.
qw1
07.04.2017 16:44Вроде как немного затронута тема с C11 и кодом ошибки (например, приведена функция strerror_s).
Стоило уделить больше внимания тому, что в C11 многие функции обзавелись вторым вариантом, который возвращает сразу и значение, и код ошибки (например, fopen и fopen_s). Примеры с функциями из C11 соседствуют с функциями C99 (в дидактических целях стоило давать все примеры с fopen_s).arielf
07.04.2017 18:29Приводить все примеры из Annex K бесполезно, ибо он почти нигде не реализован, и где мы взяли код ошибки: из errno, или вернули из функции — не принципильно.
arielf
07.04.2017 18:33Более того, ходили слухи, что его могут вообще исключить из будущих обновлений ISO C.
qw1
07.04.2017 21:33Вот, а чёртова Visual Studio тр&%ет мне мозг в warning-ах, что fopen is depracated, use fopen_s.
Хотя strcat_s и strcpy_s очень даже кстати, спасут от многих уязвимостей с переполнением буфера.arielf
07.04.2017 21:58Windoпроблемы. ;-) Ни в ISO C11, ни в POSIX они не deprecated ни в коей мере. Более того, не знаю, как в новой версии VC, а ещё недавно порядок параметров в прототипы MS функций не был совместим с Annex K из C11. То есть MS продвинули свои safe функции в ISO C, но парни из комитета взяли и сделали версии функций, не совместимые вообще ни с одной существующей реализацией! В общем, такие дела.
exchg
08.04.2017 00:05То есть MS продвинули свои safe функции в ISO C, но парни из комитета взяли и сделали версии функций, не совместимые вообще ни с одной существующей реализацией!
Как это они умудрились продвинуть мимо комитета, или как комитет мог перепринять? Помоему microsoft сами себе придумали эти функции и пометили старые как depricated.
qw1
08.04.2017 09:46особенно MS любит изобретать всякие _wfopen_s, _strncpy_l, _mbsstr, _mbstrlen и т.п.
arielf
08.04.2017 16:30Я свечу не держал, но вроде бы MS их придумали и предложили комитету, а они их сделали по своему.
Подробнее:
http://stackoverflow.com/questions/372980/do-you-use-the-tr-24731-safe-functions
http://stackoverflow.com/questions/2169016/mac-solution-for-safe-alternatives-to-unsafe-c-c-standard-library-function
https://msdn.microsoft.com/en-us/library/8ef0s5kh.aspx
arielf
07.04.2017 22:05+1Я привёл ссылку http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm в эссе.
strcat_s()
иstrcpy_s()
как раз не особо полезны, в C99 ввели 'n' функции:strncat()
иstrncpy()
. Существенная разница в том, что 's' функции не используют errno, а возвращают код ошибки. Это было бы хорошо, если б не совместимость и усложение прототипов. Очень сложный вопрос, что лучше.qw1
08.04.2017 09:53+1Ознакомился. Оказывается,
strncpy
делает лишнюю работу по обнулению всего буфера (а если брать с запасомchar fileName[4096];
это лишнее время CPU). А strncat неинтуитивна — size это вовсе не размер destination буфера, и зануление остатка не происходит.
Где-то была статья про разношёрстность стандартной библиотеки PHP, похоже C в ту же сторону идёт.exchg
08.04.2017 15:43похоже C в ту же сторону идёт.
с означенных событий прошло более 15 лет, с разношерстностью все более-менее ок.
arielf
08.04.2017 16:18Всё зависит от использования. Обычно обнуление очень удобно. Если у вас строка записалась не полностью или вообще не записалась, вы всегда можете глянуть, где она закончится. Многие наоборот считают, что новый интерфейс не интуитивен. Я его не использую, ибо в большинстве UNIX он отсутствует.
qw1
08.04.2017 16:22Если у вас строка записалась не полностью или вообще не записалась, вы всегда можете глянуть, где она закончится
Понятно, что nul character надо писать. Но только один, зачем занулять весь неиспользуемый остаток буфера. Кстати, если строка записалась не полностью (буфер короче строки), nul character не пишется в strncpy и мы получаем некорректную asciiz-строку.arielf
08.04.2017 16:36Обнуляется не весь буффер, только если длина строки короче, чем нужно было скопировать символов.
If, after copying the terminating null character from src, count is not reached, additional null characters are written to dest until the total of count characters have been written.
qw1
08.04.2017 18:56О чём я и писал — «весь неиспользуемый буфер».
char dst[4096]; strncpy(dst, "test", sizeof(dst));
Нужно в dst всего лишь 5 байт записать, а будут записаны все 4096 (4091 записанных байт — оверхед).exchg
08.04.2017 19:06char * strncpy ( char * destination, const char * source, size_t num ); Copies the first num characters of source to destination. (http://www.cplusplus.com/reference/cstring/strncpy/)
Вы неправильно используете. Не sizeof(dst), а strlen("test").
exchg
08.04.2017 19:13Ну или
strncpy(dst, "some long test", 4);
exchg
08.04.2017 19:23Хотя маны говорят что
One valid (and intended) use of strncpy() is to copy a C string to a fixed-length buffer while ensuring both that the buffer is not overflowed and that unused bytes in the target buffer are zeroed out (perhaps to prevent information leaks if the buffer is to be written to media or transmitted to another process via an interprocess communication technique).
Так что я скорее неправ.
arielf
09.04.2017 03:10Ну что же вы, честное слово. Спецификации пишут, чтобы их читали. :3
char dst[4096]; strncpy(dst, "test", strlen("test") + 1); \\ место для '\0'
n
говорит, сколько символов нужно взять изsrc
. Но вообще, strlcpy() действительно выглядит лучше.qw1
09.04.2017 13:48
Плохой пример. В таком виде никакого смысла вstrncpy(dst, "test", strlen("test") + 1); \\ место для '\0'
strncpy
нет, тут она полностью равноценнаstrcpy
qw1
08.04.2017 19:04ААА… Тут я начинаю понимать, что
strncpy
— не заменаstrcpy_s
, как это вы предлагали выше.
strncpy — это способ скопировать из некоторого буфера ограниченной длины, в котором должен быть терминирующий nul, но этот факт не гарантируется (например, буфер прочитан из внешнего файла)
Например:
struct Header { char name[20]; char title[30]; } header; // ... char localname[21]; strncpy(localname, header.name, sizeof(header.name));
arielf
09.04.2017 03:18Я ничего подобного не говорил, и не мог. Грубо говоря,
strncpy()
может копировать строки у которых отсутствует'\0'
в конце. (strcpy()
понятное дело не может). Похоже, нужно ещё одно эссе про строковые функции.kloppspb
09.04.2017 03:27Похоже, нужно ещё одно эссе про строковые функции.
Хм… Вон, на ru:SO по тегу «C» регулярно всплывают вопросы уровня «почему не работает код»:
if( a == b ) /* обе переменные, само собой, char *, и имеется в виду сравнение строк */
P.S. А вообще, IMHO, во многих ситуациях strdup() — вполне решение. Но тут уж по конкретике смотреть надо.
3aicheg
«Доктор, откуда у вас такие картинки?»
Casufi
Самому интересно стало что это такое
https://ru.wikipedia.org/wiki/Trachelophorus_giraffa
3aicheg
Спасибо товарищу Гуголю за нашу спокойную старость (в счастливом детстве поди найди по картинке, что сие).
arielf
Видите, у него и родовое название говорящее. :3
hdfan2
Fucking bugs.
arielf
Необычные жуки символизируют процесс появления ошибок.