Может кому-то пригодится.
Что делать начинающему программеру на Си, как отладить свой макрос, найти логические ошибки?
Конечно если макрос из пяти строчек кода, то и отлаживать тут особо нечего, а если он большой и если еще будет куча функций в Глобальном макросе? Можно "натыкать" по ходу текста макросов вспомогательных регистров PSW и записывать в них промежуточные результаты, на экранах наделать "левых" дисплеев с этими PSW, а после отладки искать их и удалять.
Предлагаю цивилизованный способ, при котором после отладки ничего искать и удалять ненужно. Кроме того при отладке появятся точки останова.
1. Понадобится окно для вывода отладочной информации. Во вложении есть несколько образцов таких окон.
Захват-1.png Захват-2.png
Принцип построения такого окна простой. Это
- несколько текстовых дисплеев, расположенных друг под другом. Количество регистров в дисплее желательно делать кратным 4 для корректной работы в последующем символов табуляции;
- адрес регистра первого дисплея - PSW0, с динамической адресацией через регистр PSW139
Захват-3.png
почему сделано так, будет понятно после рассмотрения работы функции вывода отладочной информации на экран.
- номера регистров последующих дисплеев являются продолжением регистров пердыдущих
Захват-4.png Захват-5.png
шрифт для дисплеев нужно выбирать моноширинный, у меня это - "Fixedsys".
- поверх дисплеев расположены "невидимые" битовые кнопки PSW136.0, PSW136.1, PSW136.2
Захват-6.png Захват-7.png
Захват-8.png Захват-9.png
2. Кроме окна нужна функция, которая и будет выводить отладочную информации на экран.
Захват-10.png
Помешаем в Глобальном макросе, например, в самом конце такую функцию
Код:
/* DEBUG Print */
void realdprintf (char const *file, int line, char const *func, unsigned delay, char const *format, ...)
{
#define winNo 5004 //Debug window number
#define LTOTAL 6 //Number of lines in the debug window
#define CTOTAL 56 //Number of characters in one line of the debug window
#define INPUT PSW[136] //Window control buttons
#define SCRADDR PSW[139] //Initial address of the screen buffer
#define TIC (*(DWORD*) (PSW + 38)) //System timer
#define TABS 8
#define OK 0
#define LEFT 1
#define RIGHT 2
#define KbdStatus(bitno) ((INPUT) & (1 << (bitno)))
#define FORWARD 1
#define BACKWARD -1
char *pBuffScr;
char *pBuffPrint = Malloc((LTOTAL -1) * CTOTAL);
BYTE *saveREGS = Malloc(LTOTAL * CTOTAL);
WORD saveREG1, saveREG2;
static int Xpos = 0, Ypos = 0;
register int t, len2;
int len1, len3, dir;
unsigned long tm;
va_list arg;
/* Save working registers */
saveREG1 = INPUT;
saveREG2 = SCRADDR;
/* Calculate the initial address of the screen buffer */
SCRADDR = PSW[10] - LTOTAL * CTOTAL/2;
pBuffScr = (char*) &PSW[SCRADDR];
/* Save the registers of the screen buffer area */
memcpy(saveREGS, pBuffScr, LTOTAL * CTOTAL);
/* Clear screen buffer */
memset(pBuffScr, ' ', LTOTAL * CTOTAL);
/* Print to screen line #1 */
len1 = sprintf(pBuffScr, "> %s line %d in %s ", func, line, file);
len2 = CTOTAL + Max(0, len1-CTOTAL);
/* remaining print lines in the buffer */
va_start(arg, format);
len3 = vsnprintf (pBuffPrint, LTOTAL * CTOTAL - len2 - 1, format, arg);
va_end(arg);
pBuffPrint[len3] = '\0';
/* Display buffer on screen */
t= 0;
do {
switch(pBuffPrint[t]) {
case '\a': //Bell
Beep();
break;
case '\t': //Tab
len2 = len2 - len2 % TABS + TABS;
break;
case '\n': //LF
len2 = len2 - len2 % CTOTAL + CTOTAL;
break;
default:
pBuffScr[len2] = pBuffPrint[t];
len2++;
}
t++;
} while (pBuffPrint[t] !=0 && len2 < (LTOTAL * CTOTAL));
/* Call the debug window and control its position */
dir = FORWARD;
tm = TIC;
while(!(KbdStatus(OK) || (delay !=0 && (TIC-tm) >= delay*10)) ) {
if (KbdStatus(RIGHT)) {
tm = TIC;
CloseWindow(winNo);
Xpos = Xpos + 100;
}
if (KbdStatus(LEFT)) {
tm = TIC;
CloseWindow(winNo);
Xpos = Xpos - 100;
if (Xpos < 0) {
Xpos = 0;
Ypos = Ypos + dir * 100;
}
if (Ypos == 400) dir = BACKWARD;
if (Ypos == 0) dir = FORWARD;
}
OpenWindow(winNo, Xpos, Ypos);
INPUT = 0;
Delay(10);
}
CloseWindow(winNo);
/* Restore working registers */
memcpy(pBuffScr, saveREGS, LTOTAL * CTOTAL);
INPUT = saveREG1;
SCRADDR = saveREG2;
/* Deallocate memory blocks */
Free(pBuffPrint);
Free(saveREGS);
}
А в самом начале Глобального макроса записываем следующие директивы препроцессора и прототип нашей функции
Код:
#define DEBUG_ENB 1
#if DEBUG_ENB
#include <stdarg.h>
#define DEBUG(...) realdprintf(__FILE__, __LINE__, __func__, __VA_ARGS__)
#else
#define DEBUG(...)
#endif
void realdprintf (char const *file, int line, char const *func, unsigned delay, char const *format, ...);
Между этими двумя фрагментами можете как обычно объявлять свои глобальные переменные и помещать свои глобальные функции.
Теперь как это работает.
В нужном месте любого своего макроса помещаете вызов такой "функции"
DEBUG(int delay, const char *format, ... ), где
- delay - время "показа" окна отладки в сек
- format - строка формата аналогичная функции fprint, в строке формата можно использовать \n - переход на новую строку, \t - табуляция, \a - короткий бип
- ... собственно сами переменные, которые нужно вывести на экран.
Например,
int a, b, c;
a= 2;
b = PSW[250];
c = a + b;
DEBUG(20, " This is my test\n a= %d b= %d c= %d", a, b, c);
PSW[300] = 100;
PSW[400] = PSW[256] + PSW[300];
DEBUG(30, "PSW[256]= %d PSW[300]= %d PSW[400]= %d", PSW[256], PSW[300], PSW[400]);