Publicado · en mejora
Guía de C · 5/6
Por ahora, este capítulo solo está disponible en inglés.
C has no exceptions. Functions report failure through return values and errno, and the caller must check every time. This chapter also covers assert, sanitizers, and lightweight unit testing.
By convention, return 0 on success and a negative code on failure, passing the real result back through a pointer (an out-parameter). Pointer-returning functions signal failure with NULL.
#include <stdio.h>
enum { OK = 0, ERR_DIV_ZERO = -1, ERR_NULL = -2 };
int safe_divide(int a, int b, int *out) {
if (out == NULL) return ERR_NULL;
if (b == 0) return ERR_DIV_ZERO;
*out = a / b;
return OK;
}
int main(void) {
int result;
int rc = safe_divide(10, 0, &result);
if (rc != OK) {
fprintf(stderr, "division failed (code %d)\n", rc);
return 1;
}
printf("%d\n", result);
return 0;
}Ignored return values are among the most common C bugs. If a call can fail, check it before using what it produced.
Many standard library functions return a sentinel such as NULL, -1, or EOF on failure and store the reason in errno from <errno.h>. strerror(errno) and perror turn the code into a readable message.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "usage: %s <file> [number]\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "cannot open %s: %s\n", argv[1], strerror(errno));
return 1;
}
fclose(fp);
if (argc > 2) {
char *end;
errno = 0; /* reset before calling strtol */
long n = strtol(argv[2], &end, 10);
if (errno == ERANGE) {
fprintf(stderr, "number out of range\n");
} else if (end == argv[2] || *end != '\0') {
fprintf(stderr, "not a number\n");
} else {
printf("n = %ld\n", n);
}
}
return 0;
}errno only means something after a failure, and successful calls do not reset it, so check the return value first. When the return value alone cannot signal an error, as with strtol, set errno = 0 beforehand. Avoid atoi, which cannot report bad input at all.
assert from <assert.h> verifies an assumption that should always hold; if it fails, it prints the file and line and aborts. Defining NDEBUG compiles every assert away, so anything that can legitimately go wrong at run time, such as user input or file contents, needs a real check instead.
if#include <assert.h>
#include <stdlib.h>
#include <string.h>
/* internal helper: caller obligations are checked with assert */
static void copy_ints(int *dst, const int *src, size_t n) {
assert(dst != NULL && src != NULL);
memcpy(dst, src, n * sizeof *dst);
}
/* public API: validate external input and report errors */
int *duplicate(const int *src, size_t n) {
if (src == NULL || n == 0) return NULL;
int *copy = malloc(n * sizeof *copy);
if (copy == NULL) return NULL; /* out of memory */
copy_ints(copy, src, n);
return copy;
}When a function acquires several resources, a common idiom is to goto a single cleanup label on failure instead of repeating release code at every exit.
Memory and overflow bugs often look harmless until they corrupt something far away. GCC and Clang sanitizers catch them the moment they happen.
gcc -std=c17 -g -O1 -fno-omit-frame-pointer \
-fsanitize=address,undefined bug.c -o bug
./bug// bug.c
#include <stdlib.h>
#include <limits.h>
int main(void) {
int *a = malloc(4 * sizeof *a);
a[4] = 1; /* ASan: heap-buffer-overflow */
free(a);
int x = INT_MAX;
x = x + 1; /* UBSan: signed integer overflow */
return x == 0;
}The report points at the offending line and, for heap errors, the allocation site. Sanitized builds are slower, so use them for development and tests, not releases. MSVC supports ASan too, via /fsanitize=address.
The standard library ships no test framework, but a small test program gets you surprisingly far. Count failures and report through the exit status, and Make, CMake, and any CI system can use it directly.
// test_mathutil.c
#include <stdio.h>
#include "mathutil.h"
static int failures = 0;
#define CHECK(cond) do { \
if (!(cond)) { \
fprintf(stderr, "%s:%d: failed: %s\n", __FILE__, __LINE__, #cond); \
failures++; \
} \
} while (0)
int main(void) {
CHECK(clamp(5, 0, 10) == 5);
CHECK(clamp(-3, 0, 10) == 0);
CHECK(clamp(42, 0, 10) == 10);
printf("%s\n", failures ? "FAILED" : "OK");
return failures ? 1 : 0;
}With CMake, add enable_testing() and add_test(NAME mathutil COMMAND test_mathutil), then run ctest from the build directory. As the suite grows, consider frameworks such as Unity, cmocka, or Check, and build tests with sanitizers to catch memory errors in the same run.
NULL for failure) and always check them.errno only after confirming a failure, and format it with strerror or perror.assert checks programmer assumptions; validate external input with ordinary if statements.-fsanitize=address,undefined during development to catch memory errors and undefined behavior.
0 comentarios
Iniciar sesión · Inicia sesión para dejar un comentario.
Sé el primero en comentar.