Bu makro suiiste'molmi?

Men ba'zi kodlarni teskari muhandislik qildim va bunga duch keldim ...

/************************************************************************/
/*                                                                      */
/*                          MACRO CHECK_FREAD                           */
/*                                                                      */
/*  CHECK_FREAD is used to check the status of a file read.  It         */
/*   is passed the return code from read and a string to print out if   */
/*   an error is detected.  If an error is found, an error message is   */
/*   printed out and the program terminates.  This was made into a      */
/*   macro because it had to be done over and over and over . . .       */
/*                                                                      */
/************************************************************************/

#define CHECK_FREAD(X, msg)  if (X==-1) \
                 { \
                return(DCD_BADREAD); \
                 }

Asosan, ushbu fayldagi muayyan funktsiyalar to'plami, natijada xatolik mavjudligini tekshirish uchun (c-o'qish) qilganda buni chaqiradi. Ularda EOF uchun ham xuddi shunday tekshiruv bor... Asosan aytishim mumkinki, ular bir nechta joylarda o'z funksiyalarining o'rtasida xatolik qaytarilishini kiritish uchun shunday qilishmoqda.

e.g.

/*  Read in an 4                */
    ret_val = read(fd, &input_integer, sizeof(int32));

    CHECK_FREAD(ret_val, "reading an 4");
    CHECK_FEOF(ret_val, "reading an 4");

    if (input_integer != 4)
    {
        return(DCD_BADFORMAT);
    }

    /*  Read in the number of atoms         */
    ret_val = read(fd, &input_integer, sizeof(int32));
    *N = input_integer;

    CHECK_FREAD(ret_val, "reading number of atoms");
    CHECK_FEOF(ret_val, "reading number of atoms");

Endi men c/c++ dasturlashiga qaytyapman va men hech qachon makrolardan ko'p foydalanmaganman, lekin bu makro suiiste'molmi?

Men buni o'qidim... C++ makroslari qachon foydali?

... va bu hech qanday kosher misoliga o'xshamaydi, shuning uchun mening taxminim "HA" bo'ladi. Lekin men shunchaki bilimli taxminlar qilishdan ko'ra ko'proq ma'lumotga ega bo'lishni xohlardim ... :)

...errr, qandaydir o'rashni qo'llash yaxshiroq emasmi?


person Jason R. Mick    schedule 02.09.2010    source manba
comment
Ushbu makros sharhlardagi tavsifiga ko'ra xato xabarini chop etmaydi. Bu toʻliq kodmi?   -  person Kirill V. Lyadvinsky    schedule 02.09.2010
comment
Ha, bu to'liq kod. Va ChrisFga men qo'shgan misolga qarang.   -  person Jason R. Mick    schedule 02.09.2010
comment
Va nima uchun "msg" argumenti ishlatilmaydi? Sharhlar amalga oshirishga mos kelmaydi, demak, kamida bittasida xatolik bor, lekin ehtimol ikkalasi ham. size_t checked_fread(void *buffer, size_t itemsize, size_t numitems, FILE *fp, const char *msg) funksiyasini yozish va undan izchil foydalanish yaxshiroq bo'lar edi. Nima uchun -1 ga qarshi tekshirilayotgani haqida taxmin qilish ham qiziq; fread() 0 yoki xatoning qisqa hisobini qaytaradi - va qaytarish turi size_t bo'lib, deyarli barcha maqsadlar uchun -1 qaytish qiymatini istisno qiladi.   -  person Jonathan Leffler    schedule 02.09.2010
comment
Lekin bu suiiste'mol qilingan deb hisoblanadimi? Bu mening savolim. @Jonathan Men nima uchun ular xabarni ishlatmayotganini bilmayman ... unutuvchanlik? dangasa? chalkashlik? siz tanlaysiz! Lekin siz mutlaqo haqsiz, g'alati....   -  person Jason R. Mick    schedule 02.09.2010
comment
Ehtimol, ular kodni sinab ko'rishda msg atributidan foydalanganlar va uni chiqarishdan oldin chop etish kodini olib tashlashgan. Men buni birinchi marta kodga sharhlar kiritish usuli deb o'yladim, lekin nega buning o'rniga halol /* sharhlar */ ishlatmaslik kerak?   -  person teukkam    schedule 02.09.2010
comment
Suiiste'mol va noto'g'ri foydalanish? Aytish qiyin... msj ishlatilmagani uchun, bu biroz ma'nosiz. Shubhasiz, fread() haqidagi sharhlar read() ga tegishli emas. Biz CHECK_FEOF() ni ko'ra olmaganimiz uchun bu nima qilishini ayta olmaymiz. Biroq, menimcha, ikkala shartni ham boshqarish uchun oddiy funksiya yaxshiroq bo'lardi: u read() funksiyasini (tizim chaqiruvi) o'rashi va xato holatini hamma narsadan ajratishi mumkin: if ((retval = wrapped_read(fd, &input_integer, sizeof(int32), "reading a 4")) != DCD_NOERROR) return retval;. Va agar 4 uchun test keng tarqalgan bo'lsa, men buni yana davom ettiraman. Menimcha, bu makro suiiste'mol.   -  person Jonathan Leffler    schedule 02.09.2010


Javoblar (8)


Chunki u bir xil emas. Agar siz xuddi shu narsani funktsiyaga qo'ysangiz, X -1 ga teng bo'lsa, DCD_BADWRITE ni qaytaradigan qisqa funksiyaga ega bo'lasiz. Biroq, ko'rib chiqilayotgan makros o'z ichiga olgan funksiyadan qaytadi. Misol:

int foobar(int value) {
    CHECK_FREAD(value, "Whatever");
    return 0;
}

kengaytiriladi:

int foobar(int value) {
    if (value == -1) {
        return (DCD_BADWRITE);
    };
    return 0;
}

Agar bu funktsiya bo'lsa, tashqi funktsiya (foobar) natijani mamnuniyat bilan e'tiborsiz qoldiradi va 0 ni qaytaradi.

Funktsiyaga juda o'xshab ketadigan, lekin hal qiluvchi tarzda o'zini boshqacha tutadigan so'l asosiy IMO yo'q. Men ha, deyman, bu makrosni suiiste'mol qilish.

person tdammers    schedule 02.09.2010
comment
Agar ular bir necha tonna qiymatlarni o'qiyotgan bo'lsa va takroriy kodni yo'q qilishni xohlasa, qanday alternativa bor? Ular qilayotgan ishni qilishning yaxshi usuli bormi (misol uchun mening tahrirlangan postimga qarang)? Men oldindan o'ylashim mumkin bo'lgan yagona narsa shundaki, ular o'qish jarayonini turli qismlarga bo'lishlari kerak edi, chunki faylda turli bo'limlar mavjud ... - person Jason R. Mick; 02.09.2010
comment
C++ da istisnolar. Kodni xatolik haqida xabar berish uchun qaytarish qiymatlarini suiiste'mol qilish o'rniga xatoga yo'l qo'yadigan kichik funktsiyaga qo'ying. Keyin foydalanish kodida hamma narsani try / catch ga qo'ying va catch dan DCD_BADWRITE ni qaytaring. Agar u oddiy C bo'lsa, unda setjmp - uslubdagi xatolarni qayta ishlash, ehtimol yaxshiroq yechimdir. Va keyin, ehtimol, gipotetik dastur birinchi navbatda pastadirdan foydalanish yaxshiroq bo'lar edi. - person tdammers; 02.09.2010
comment
@Jason: Bu erda takrorlanuvchi kodni yo'q qilishni xohlash qonuniy deb o'ylamayman. Ayniqsa, barcha xabarlar butunlay keraksiz ekanligini hisobga olsangiz, ularning o'chirishdan keyingi kodi if (retval != -1) return DCD_BADREAD dan sezilarli darajada qisqa emas. Albatta, yozish yoki tushunish oson emas. - person Steve Jessop; 03.09.2010
comment
Faqatdan foydalanishga arziydigan (funktsiyani emas, balki matnni almashtirish kerak bo'lgan) makrosdan foydalanmaslikni aytish biroz kulgili. - person alternative; 03.09.2010

Buni funksiya sifatida bajarish mumkin emas, chunki return makro ichidagi blok emas, balki chaqiruv funksiyasidan qaytmoqda.

Bu makrolarga yomon nom beradigan narsadir, chunki u boshqaruv mantiqining muhim qismini yashiradi.

person Mark Ransom    schedule 02.09.2010
comment
C++ da bu turdagi narsalar uchun throw dan foydalanish kerak. C tilida mantiqiy nomlangan so'l ko'pincha eng yaxshi alternativ hisoblanadi. Qo'shimcha ma'lumot olish uchun mening to'liq javobimni ko'ring. - person supercat; 03.09.2010

Men bunday so'lni *abuse* deb atash kerakmi, deb o'ylayapman, lekin bu yaxshi fikr emasligini aytaman.

Umuman olganda, siz makroda return dan foydalanishdan qochishingiz kerak deb bahslashaman. Qaytishni to'g'ri bajaradigan so'l yaratish qiyin, uni noqulay yoki tushunish qiyin deb ataydigan funktsiyani boshqarish oqimini yaratmasdan. Siz o'zingiz bilmagan holda funktsiyadan "tasodifan" qaytishni xohlamaysiz. Bu disk raskadrovka uchun zerikarli bo'lishi mumkin.

Bundan tashqari, ushbu makro qanday ishlatilishiga qarab kod oqimi bilan bog'liq muammolarga olib kelishi mumkin. Masalan, kod

if (some_condition)
    CHECK_FREAD(val, "string")
else
    something_else();

siz o'ylagan narsani qilmaydi (else makro ichidagi if bilan bog'lanadi). Atrofdagi shartlarni o'zgartirmasligiga ishonch hosil qilish uchun so'l { } ichiga kiritilishi kerak.

Bundan tashqari, ikkinchi argument ishlatilmaydi. Bu erda aniq muammo - bu hujjatlarga mos kelmaydigan dastur. Yashirin muammo, so'l quyidagi tarzda chaqirilganda:

char* messages[4];          // Array of output messages
char* pmsg = &messages[0];
....
CHECK_FREAD(val, pmsg++);

Makros chaqirilgandan so'ng ko'rsatgich massivning keyingi elementiga o'tishini kutasiz. Biroq, ikkinchi parametr ishlatilmaydi, shuning uchun o'sish hech qachon sodir bo'lmaydi. Bu yon ta'siri bo'lgan bayonotlar makrolarda muammolarga olib kelishining yana bir sababidir.

Bularning barchasini bajarish uchun so'ldan foydalanish o'rniga, nega read va xato tekshiruvlarini inkapsulyatsiya qilish funksiyasini yozmaysiz? Muvaffaqiyatli yoki xato kodini nolga qaytarishi mumkin. Xatolarni qayta ishlash kodi barcha read bloklari orasida umumiy bo'lsa, siz shunday qilishingiz mumkin:

int your_function(whatever) {
    int ret;
    ...

    if ((ret=read_helper(fd, &input_integer)) != 0)
        goto error_case;

    ...

    // Normal return from the function
    return 0;

error_case:
    print_error_message_for_code(ret);
    return ret;
}

read_helper ga qilgan barcha qo'ng'iroqlaringiz o'zlarining qaytish qiymatini ret ga belgilagan ekan, siz butun funksiya davomida bir xil xatolik kodini qayta ishlatishingiz mumkin. Sizning funksiyangiz print_error_message_for_code oddiygina xato kodini kiritish sifatida qabul qiladi va unga mos keladigan xato xabarini chop etish uchun switch yoki massivdan foydalanadi.

Tan olamanki, ko'p odamlar goto dan qo'rqishadi, lekin bir qator ichki bloklar va shart o'zgaruvchilari o'rniga umumiy xatolarni qayta ishlash kodini qayta ishlatish buning uchun to'g'ri foydalanish holati bo'lishi mumkin. Uni toza va o'qilishi mumkin bo'lgan holda saqlang (har bir funktsiya uchun bitta yorliq va xatolarni boshqarish kodini oddiy saqlang).

person bta    schedule 02.09.2010

Bu suiiste'molmi yoki yo'qmi - bu ta'mga bog'liq. Lekin men u bilan ba'zi asosiy muammolarni ko'rmoqdaman

  1. hujjatlar butunlay noto'g'ri
  2. amalga oshirish if va mumkin bo'lgan osilgan else muammosi tufayli juda xavflidir
  3. nomlash bu atrofdagi funktsiyaning dastlabki qaytishi ekanligini bildirmaydi

shuning uchun bu juda yomon uslub.

person Jens Gustedt    schedule 02.09.2010

Agar so'l manba faylida bo'lsa va faqat shu faylda ishlatilsa, men uni biron bir sarlavhada o'chirilganidan ko'ra biroz kamroq haqoratomuz deb bilaman. Lekin menga qaytariladigan makros (ayniqsa, ilovani tugatish uchunhujjatlangan va aslida qaytariladigan) va shartli ravishda qaytariladigan makroni unchalik yoqtirmayman, chunki u yaratishni juda oson qiladi. quyidagi xotira oqishi:

char *buf = malloc(1000);
if (buf == 0) return ENOMEM;

ret_val = read(fd, buf, 1000);
CHECK_READ(ret_val, "buffer read failed")

// do something with buf

free(buf);

Agar men hujjatlarga ishonsam, o'qish muvaffaqiyatsiz tugashi bilan ushbu kod xotirani oqizishiga shubha qilish uchun hech qanday asos yo'q. Hujjatlar to'g'ri bo'lsa ham, men C dagi boshqaruv oqimini yaqqol ko'rishni afzal ko'raman, ya'ni makrolar tomonidan yashirilmaydi. Bundan tashqari, men kodimni tozalash uchun resurslarim bo'lgan holatlar va men yo'q bo'lgan holatlar o'rtasida noaniq izchil ko'rinishini afzal ko'raman, makroni biriga ishlatishdan ko'ra, boshqasi uchun emas. Shunday qilib, so'l mening didim uchun emas, hatto u qat'iy suiiste'mol bo'lmasa ham.

Hujjatlarda aytilgan narsani qilish uchun men shunday bir narsa yozishni afzal ko'raman:

check(retval != -1, "buffer read failed");

check funksiya bo'lgan holda. Yoki assert dan foydalaning. Albatta, agar NDEBUG o'rnatilmagan bo'lsa, tasdiqlash hamma narsani qiladi, shuning uchun agar siz alohida disk raskadrovka va reliz tuzilmalarini rejalashtirmoqchi bo'lsangiz, bu xatolarni qayta ishlash uchun yaxshi emas.

Ibratli amalni bajarish uchun men makrosni butunlay o'tkazib yuborishni afzal ko'raman va yozaman:

if (retval == -1) return DCD_BADREAD;

Bu juda uzoq kod qatori emas va uning turli misollarini birlashtirishdan haqiqiy foyda yo'q. Mavjud, yaxshi hujjatlashtirilgan vositalarni inkapsulyatsiya qilish/yashirish orqali narsalarni yaxshilayman deb o'ylash ham yaxshi fikr emas, bu orqali read xatolarni ko'rsatadi, buni C dasturchilari yaxshi tushunadilar. Boshqa hech narsa bo'lmasa, bu tekshiruv hech qanday sababsiz read siz so'raganingizdan kamroq ma'lumot ishlab chiqarishi mumkinligiga e'tibor bermaydi. Bu to'g'ridan-to'g'ri makroning aybi emas, lekin makroning orqasidagi barcha g'oya noto'g'ri xatolarni tekshirishdan kelib chiqqanga o'xshaydi.

Ehtimol, bu erda nima sodir bo'lgan bo'lsa, ular ilgari tugatilgan makrosga ega edilar, keyin ular muvaffaqiyatsizliklar yuz berganda darhol dasturni to'xtatib qo'ymaslikka qaror qilishdi, buning o'rniga DCD_BADREAD xato kodini qaytarishni xohlashdi. Ular qilishlari kerak bo'lgan narsa makroni butunlay olib tashlash va barcha chaqiruvchi saytlarni o'zgartirish edi - agar siz funktsiyani boshqarish mantiqini o'zgartirsangiz va u qaytaradigan narsani o'zgartirsangiz, bu funktsiyaning manbasini ko'rinadigan darajada o'zgartirgan ma'qul. Buning o'rniga ular xohlagan natijani beradigan minimal mumkin bo'lgan o'zgarishlarni amalga oshirishdi. Ehtimol, o'sha paytda bu yaxshi fikr bo'lib tuyuldi (va sharhlarni yangilashni unutish, afsuski, keng tarqalgan xatodir). Ammo hamma rozi bo'lganidek, bu juda xavfli kodga olib keldi.

Boshqarish oqimini amalga oshiradigan makrolarbo'lishi mumkin suiiste'mol bo'lmasligi mumkin, lekin menimcha, ular faqat boshqaruv oqimiga o'xshab ko'rinadigan va aniq hujjatlashtirilgan holatlarda. Saymon Tathamning birgalikdagi makroslari yodga tushadi - ba'zi odamlar birinchi navbatda birgalikdagi tartiblarni yoqtirmaydilar, lekin siz bu shartni qabul qilsangiz, crReturn deb nomlangan makro hech bo'lmaganda ko'rinadi u qaytib keladiganga o'xshaydi. .

person Steve Jessop    schedule 02.09.2010
comment
Katta ehtimol bilan, disk raskadrovka vaqtida xabardagi qiymatni qayd qiluvchi va qaytaradigan kodni yoqish foydali bo'ladi. Ishlab chiqarish jarayonida ro'yxatga olish kodi kerak emas. Makrosni joyida qoldirish, agar kerak bo'lsa, jurnalni qayta yoqish imkonini beradi. - person supercat; 03.09.2010
comment
@supercat: Shunday qilib, hujjatlar faylda hech qanday kod mavjud bo'lmagan disk raskadrovka xatti-harakatlarini tavsiflaydi, lekin disk raskadrovka dasturchisi kerak bo'lganda amalga oshirishi va kiritishi kerakmi? Ishlab chiqarish harakati o'rniga qaysi kod mavjud? Keyin ha, bu shubhasiz ulkan suiiste'mol ;-) - person Steve Jessop; 03.09.2010
comment
Ehtimol, jurnal kodi o'tmishda mavjud bo'lgan, ammo o'chirilgan. Mening afzal ko'rganim, qayd qilish qismi uchun ichki o'rnatilgan makrosdan foydalanish va biror narsa jurnalga kirish uchun o'sha ichki so'lni aniqlash yoki hech narsa qilmaslik uchun LOGGING_ENABLE bayrog'idan foydalanish edi. - person supercat; 03.09.2010

Men so'lni suiiste'mol qilish misolini ikkita sababga ko'ra ko'rib chiqaman: (1) Makrosning nomi nima qilishini aniq ko'rsatmaydi, ayniqsa, u qaytishi mumkin; (2) Makros gapga sintaktik jihatdan ekvivalent emas. Kod kabi

  if (someCondition)
    CHECK_FREAD(whatever);
  else
    do_something_else();

muvaffaqiyatsiz bo'ladi. Men afzal ko'rgan o'zgarish:

#define LOG_AND_RET_ERROR(msg) do {LOG_ERROR(msg); return DCD_BADREAD;} while(0)
  if (x==-1) LOG_AND_RET_ERROR(msg);
person supercat    schedule 02.09.2010

Xo'sh, agar siz qisqa funksiyani aniqlasangiz, har bir chaqiruv saytida ko'proq belgilar kiritishingiz kerak. ya'ni

CHECK_FREAD(X, msg)

vs.

if (check_fread(X, msg)) return DCD_BADREAD;
person maxschlepzig    schedule 02.09.2010

Amalga oshirilgan bo'shliq doirasida u yaxshi bo'lishi mumkin edi (garchi u odatda C++ da norozi bo'lsa ham).

Meni xavotirga soladigan eng yorqin narsa shundaki, ba'zi yangi ishlab chiquvchi buni ko'rib, shunday qilishni o'ylaydi.

if (CHECK_FREAD(...)) {
   /* do stuff here */
} else {
   /* error handle */
}

Bu noto'g'ri ekanligi aniq.

Bundan tashqari, vaziyatni yomonlashtiradigan DCD_BADREAD da iste'mol qilinmasa/kutilgan bo'lmasa, msg ishlatilmaganga o'xshaydi...

person ezpz    schedule 02.09.2010
comment
Yangi kiritilgan ishlab chiquvchilar ahmoqona xatolarga yo'l qo'yishadi, masalan, funktsiyaning (yoki ifoda natijasining) qaytariladigan qiymatini uning aslida nima ekanligini qidirmasdan ishlatish kabi. Buni hisobga olishning ma'nosi yo'q. - person Steve Jessop; 02.09.2010
comment
@Stiv: Boshqa tomondan, ularni ayniqsa chalkash makrolar bilan taqdim etish yomon va samarasiz ko'rinadi. Ayniqsa, so'l noto'g'ri tuzilgan va hujjatlar noto'g'ri bo'lsa. - person David Thornley; 02.09.2010
comment
To'g'ri, lekin bu yaxshi so'l bo'ladimi yoki yomonmi (va men bu yomon ekanligiga qo'shilaman), xuddi shu narsani ifodalash uchun kengaytirilmaydigan har qanday makro haqida ham aytish mumkin: agar kimdir if (MACRO(...)) { ... } else { ... } deb yozadi, keyin noto'g'ri bo'ladi. - person Steve Jessop; 03.09.2010