Odpowiedzi 195
C
while (ch = (getchar() != EOF))
Jest to więc próba przypisania wartości do zmiennej ch, którą to próbę kompilator traktuje jako błędne użycie operatora „=” zamiast „==”.
2a. Najprostszym sposobem uchronienia się przed niespodziewanym użyciem liczb ósemkowych jest wprowadzenie opcjonalnego zakazu używania takich liczb za pomocą odpowiedniej opcji kompilatora.
2b. Kompilator mógłby wychwytywać wszystkie przypadki użycia operatorów & oraz | w instrukcji if oraz złożonych porównaniach w sytuacji, gdy brak jest jawnego porównania z wartością 0. Tak więc instrukcja:
if (u & 1) /* czy u jest nieparzyste? */
musiałaby zostać zmieniona na
if ((u & 1) != 0) /* czy u jest nieparzyste? */
2c. W przypadku poszukiwania niezamierzonych początków komentarza podejrzany jest każdy komentarz rozpoczynający się od znaku alfabetycznego albo nawiasu otwierającego. Jeśli nakażesz kompilatorowi wychwytywać wszystkie takie przypadki, łatwo pozbędziesz się ostrzeżeń z jego strony, wprowadzając co najmniej jedną spację pomiędzy gwiazdką w ograniczniku a wspomnianym alfabetykiem lub nawiasem:
/*Ten komentarz powodować będzie generowanie ostrzeżenia */
/* Ten nie, ze względu na spację rozdzielającą */
/*-----Ten komentarz także nie wzbudzi zastrzeżeń-------*/
Należy ponadto eliminować z kodu wątpliwe przypadki i jasno wyrażać swe intencje zapisem:
quot = numer/ *pdenom
albo
quot = numer/(*pdenom)
zamiast
quot = numer/*pdenom
2d. Kompilator mógłby na przykład kwestionować wszelkie przypadki łączenia operatorów bitowych z arytmetycznymi, jeżeli brak jest nawiasów wyznaczających explicite kolejność obliczeń. Instrukcja:
word = bHigh << 8 + bLow;
musiałaby wówczas zostać zapisana w jednej z poniższych postaci:
word = (bHigh << 8) + bLow;
word = bHigh << (8 + bLow);
zależnie od faktycznych intencji programisty.
Na podobnej zasadzie można by wykrywać łączenie dwóch operatorów o różniącym się priorytecie, bez użycia dodatkowych nawiasów. Zasada ta jest oczywiście zbyt prymitywna, by stosować ją w praktyce in extenso, jednakże sama idea jest warta uwagi i wzbogacenie jej o jakąś dodatkową heurystykę mogłoby przydać jej znaczenia praktycznego. Heurystyka ta nie powinna być oczywiście zbyt prymitywna — w szczególności nie powinny być kwestionowane konstrukcje takie, jak:
word = bHigh*256 + bLow;
if (ch == ' ' || ch == '\t' || ch == '\n')
if (wyrażenie1)
if (wyrażenie2)
. . .else
. . .
. . . else
Aby uniknąć niepotrzebnych ostrzeżeń, należałoby ujmować w nawiasy klamrowe każdą uwarunkowaną instrukcję:
{
. . .}
else
}
/* ustalenie platformy wynikowej */
#if INTEL8080
…
#elif INTEL80x86
#elif MC680x0
#endif
należałoby wówczas zmienić do następującej postaci:
#if defined(INTEL8080)
#elif defined(INTEL80x86)
#elif defined(MC680x0)
Jednocześnie wspomniany przełącznik nie powinien powodować generowania ostrzeżenia w przypadku napotkania niezdefiniowanego makra w instrukcji #ifdef.
ASSERTMSG(pbTo >= pbFrom+size || pbFrom >= pbTo+size,
"memcpy: bloki nakładają się");
Prezentowana na następnej stronie implementacja makra ASSERTMSG powinna znaleźć się w dołączonym pliku nagłówkowym:
#ifdef DEBUG
void _AssertMsg(char *strMessage); /* prototyp */
#define ASSERTMSG(f,str) \
if (f) \
{} \
else \
_AssertMsg(str)
#else
#define ASSERTMSG(f,str)
Wykorzystywana funkcja _AssertMsg ma następującą definicję — powinna ona znaleźć się w konwencjonalnym pliku źródłowym:
void _AssertMsg(char *strMessage)
fflush(NULL);
fprintf(stderr, "\n\nNiespełniona asercja - %s\n",
strMessage );
fflush(stderr);
abort()
Alternatywne rozwiązanie polega na takim zaimplementowaniu makra ASSERT, by wszystkie jego wywołania w danym pliku posługiwały się w zamierzony sposób tą samą kopią łańcucha zawierającego nazwę pliku. Należy w związku z tym zdefiniować jeszcze jedno makro — ASSERTFILE — i wywoływać je jednokrotnie na początku każdego pliku źródłowego:
#include <stdio.h>
.
#include <debug.h>
ASSERTFILE(__FILE__)
void *memcpy(void *pvTo, void *pvFrom, size_t size)
byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom;
ASSERT(pvTo != NULL && pvFrom != NULL);
Jak widać, sposób wywołania makra ASSERT nie zmienił się, zmieniła się natomiast jego definicja:
#define ASSERTFILE(str) \
static char strAssertFile[] = str;
#define ASSERT(f) \
_Assert(strAssertFile, __LINE__)
#define ASSERTFILE(str)
#define ASSERT(f)
Gdy używa się makra ASSERT w tej postaci, można niekiedy zaoszczędzić sporo pamięci — podczas testowania przykładowego kodu niniejszej książki udało mi się zaoszczędzić 3 kB danych.
Problem z użyciem asercji w prezentowanym przykładzie polega na tym, iż testowane wyrażenie musi być wartościowane także w handlowej wersji programu. Obecna wersja, przy niezdefiniowanym symbolu DEBUG wpadnie w długotrwałą pętlę kończącą się w sposób losowy, gdy bajt wskazywany przez pch zawierać będzie znak nowej linii. By uniknąć tej przykrej pułapki, należałoby przepisać funkcję getline w sposób następujący:
void getline(char *pch)
int ch; /* ch musi być zadeklarowane jako int */
do
ch = getchar();
ASSERT(ch != EOF);
while ((*pch++ = ch) != '\n');
Jednym ze sposobów jest umieszczenie „fałszywej” asercji w domyślnej części instrukcji switch, do której sterowanie nie ma prawa trafić — o ile oczywiście każdy możliwy wariant selektora obsługiwany jest w sposób jawny:
default:
ASSERT(FALSE); /* bezwarunkowe załamanie */
break;
Wzorzec dla każdej pozycji tablicy powinien zawierać się w pewnej stowarzyszonej z nią masce; jeżeli przykładowo maska ma postać 0´FF00, w młodszym bajcie wzorca wszystkie bity muszą być zerowe, w przeciwnym razie dopasowanie wzorca dla dowolnej instrukcji nie będzie możliwe. W związku z tym funkcja CheckIdInst musi zostać uzupełniona o stosowną weryfikację:
void CheckIdInst(void)
indenfity *pid, *pidEarlier;
instruction inst;
for (pid = &idInst[0]; pid->mask != 0; pid++)
/* upewnij się, że wzorzec jest zgodny z maską */
ASSERT((pid->pat & pid->mask) == pid->pat);
Należy zweryfikować, za pomocą odpowiednich asercji, poprawność ustawień związanych z instrukcją reprezentowaną przez inst:
instruction *pcDecodeEOR(instruction inst, instruction *pc,
opcode *popc)
/* czy przypadkowo nie jest to instrukcja CMPM lub CMPA.L ? */
ASSERT(eamode(inst) != 1 && mode(inst) != 3);
...
Yoostynka