C'de debug makrolari

Bu sıralar okulda aldığım programlamaya giriş dersinden dolayı ciddi anlamda C ile içli dışlı oluyorum. En başlarda biraz mesafeli davransam da zamanla beni kendisine çekmeye başardı diyebilirim. Düşük seviyede çalışmanın kazandırdığı hız, bu işin karmaşıklığını gözardı etmeme sebep oluyor. Ve bu anlatmaya çalıştığım hız gerçekten beni yeterince tatmin ediyor. Tabi C'ye sadece hızlı demek büyük ayıp olur. İşletim sistemi tarafında da diğer dillere göre çok alt seviyede rahatça hareket edebiliyorsunuz. Özellikle GNU/Linux dünyası içerisinde yer alan programları yeniden yazmaya çalışmak da ayrı bir zevkli oluyor.

C ile ilk uğraşlarım klasik olarak önce syntaxı öğrenmek oldu. Daha sonra da zamanla incelikleri ve kütüphaneleri öğrenmeye başladım. Hala da öğrenmeye devam ediyorum. Bu öğrenme aşamalarında da çok fazla kod yazmaya çalışıyorum. İrili ufaklı birçok kod yazdım. Bir kısmı program denilecek seviyede iken diğerleri sadece ufak denemelerden oluşuyor. Buna rağmen 2 haftada 6-7 bin satır sadece C kodu yazdığımı da farkettim. Ve belirli bir seviyeye geldiğime inanıyorum. Bundan sonra yazacağım kodların biraz daha ciddi programlar olmasını umuyorum.

İşte bu tarz programlarda hata ayıklama için biraz efor sarf etmek gerekiyor. Bu amaç için çıktığım arayışta aşağıdaki gayet minimal ama işimi gören bir makrolar topluluğu ile karşılaştım.

#ifndef __dbg_h__
#define __dbg_h__

#include <stdio.h>
#include <errno.h>
#include <string.h>

#ifdef NDEBUG
#define debug(M, ...)
#else
#define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#endif

#define clean_errno() (errno == 0 ? "None" : strerror(errno))

#define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_warn(M, ...) fprintf(stderr, "[WARN] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)

#define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define sentinel(M, ...)  { log_err(M, ##__VA_ARGS__); errno=0; goto error; }

#define check_mem(A) check((A), "Out of memory.")

#define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; }

#endif

Bu kodu dbg.h olarak kaydedip kullanmak istediğiniz programa #include "dbg.h" şeklinde ekleyebilirsiniz. Daha sonra da kodunuzun ilgili satırlarda karşılaşabileceğiniz olası hataları rahatça çözebilirsiniz. Kullanim sekline gelirsek log_err, log_warn, log_info isimlerinden de anlaşılacağı gibi hata uyarı ve bilgi verme amacıyla kullanılabilir. check makrosu ise verilen argümanın doğruluğu test eder ve eğer doğru değilse stderr'e hatayi yazip error isimli etikete zıplar ve oradaki kodları çalıştırır. check_mem ise işletim sisteminden dinamik olarak alan istediğimiz de bu bellek alanının bize tahsis edilip edilmediğini kontrol edebilmemizi sağlar. check isimli makro da verilen argümanın doğruluğunu test edip duruma göre hata verip error isimli etikete zıplar.

Bu makroların nasıl kullanılabileceğine dair buradaki link size gayet yardımcı olacaktır. Zaten anlaşılmayacak kadar zor birşey yok. Önemli olan bu makroları sürekli olarak kodunuzda hata çıkartabileceği muhtemel yerlerde kullanmayı alışkanlık haline getirmek. Sanki heryerden hata fışkıracakmış gibi kod yazmak insana çok fazla şey katacağına eminim. Ayrıcana sadece bu makrolar için değil de Learn C The Hard Way sitesindeki tüm dökümanlar bence bulabileceğiniz en kaliteli C dökümanları diyebilirim. En azından bir süre C ile uğraşmış kişilerin kesinlikle göz atması gerektiğini düşünüyorum.

Yukarıda bahsettiğim makroların kullanımına dair kod da aşağıdaki gibi.

#include "dbg.h"
#include <stdlib.h>
#include <stdio.h>


void test_debug()
{
    // notice you don't need the \n
    debug("I have Brown Hair.");

    // passing in arguments like printf
    debug("I am %d years old.", 37);
}

void test_log_err()
{
    log_err("I believe everything is broken.");
    log_err("There are %d problems in %s.", 0, "space");
}

void test_log_warn()
{
    log_warn("You can safely ignore this.");
    log_warn("Maybe consider looking at: %s.", "/etc/passwd");
}

void test_log_info()
{
    log_info("Well I did something mundane.");
    log_info("It happened %f times today.", 1.3f);
}

int test_check(char *file_name)
{
    FILE *input = NULL;
    char *block = NULL;

    block = malloc(100);
    check_mem(block); // should work

    input = fopen(file_name,"r");
    check(input, "Failed to open %s.", file_name);

    free(block);
    fclose(input);
    return 0;

error:
    if(block) free(block);
    if(input) fclose(input);
    return -1;
}

int test_sentinel(int code)
{
    char *temp = malloc(100);
    check_mem(temp);

    switch(code) {
        case 1:
            log_info("It worked.");
            break;
        default:
            sentinel("I shouldn't run.");
    }

    free(temp);
    return 0;

error:
    if(temp) free(temp);
    return -1;
}

int test_check_mem()
{
    char *test = NULL;
    check_mem(test);

    free(test);
    return 1;

error:
    return -1;
}

int test_check_debug()
{
    int i = 0;
    check_debug(i != 0, "Oops, I was 0.");

    return 0;
error:
    return -1;
}

int main(int argc, char *argv[])
{
    check(argc == 2, "Need an argument.");

    test_debug();
    test_log_err();
    test_log_warn();
    test_log_info();

    check(test_check("ex20.c") == 0, "failed with ex20.c");
    check(test_check(argv[1]) == -1, "failed with argv");
    check(test_sentinel(1) == 0, "test_sentinel failed.");
    check(test_sentinel(100) == -1, "test_sentinel failed.");
    check(test_check_mem() == -1, "test_check_mem failed.");
    check(test_check_debug() == -1, "test_check_debug failed.");

    return 0;

error:
    return 1;
}
comments powered by Disqus