Всем доброго дня. Непонятная ситуация с функцией sprintf -- есть массив строк, пытаюсь менять с её помощью одну строку, и произвольным образом меняются другие. Проблема в чём -- идет вызов sprintf(initStr[numStr], "%s%d", initStr[numStr], IRData); где numStr например, 4, а вдобавок меняются строки 2 и 3. Код (C++): #include "U8glib.h" #include "rus6x10.h" #include "IRremote.h" #define MAXLOC 5 U8GLIB_ST7920_128X64 u8g(10, 11, 12, U8G_PIN_NONE); byte NumLoc=0; byte idLoc[MAXLOC]; byte secLoc[MAXLOC]; byte dirLoc[MAXLOC]; IRrecv irrecv(7); decode_results results; char* initStr[7]; void scroll(void) { for (byte i=0;i<6;i++) { initStr[i]=initStr[i+1]; } } void drawInit(byte num) { u8g.firstPage(); do { for (byte i=0; i<num; i++) { u8g.drawStr(2,10+10*i,initStr[i]); } } while(u8g.nextPage()); } byte IRRead(void) { int IRRes; while(true) { if (irrecv.decode(&results)) { IRRes=results.value; irrecv.resume(); switch (IRRes) { case 0x2FD807F: return 1; case 0x2FD40BF: return 2; case 0x2FDC03F: return 3; case 0x2FD20DF: return 4; case 0x2FDA05F: return 5; case 0x2FD609F: return 6; case 0x2FDE01F: return 7; case 0x2FD10EF: return 8; case 0x2FD906F: return 9; case 0x2FD00FF: return 0; case 0x2FD50AF: return 96; } } } } void setup(void) { byte IRData; byte numStr=2; initStr[0]="Ввод первичных данных"; initStr[1]="Используйте пульт"; initStr[2]="К-во составов: "; Serial.begin(9600); irrecv.enableIRIn(); u8g.setFont(rus6x10); drawInit(numStr); while(IRData != 96) { IRData=IRRead(); if (IRData != 96) { sprintf(initStr[numStr], "%s%d", initStr[numStr], IRData); drawInit(numStr); NumLoc=NumLoc*10+IRData; } } numStr++; if (NumLoc>MAXLOC) { initStr[numStr++]="К-во составов больше"; initStr[numStr++]="предельного! Снимите"; initStr[numStr++]="лишние и переинициа-"; initStr[numStr++]="лизируйте систему."; scroll(); drawInit(6); while(true){} } for (byte ii=0; ii<NumLoc; ii++) { sprintf(initStr[numStr], "%s%d%s", "id ",ii+1,"-го: "); if (numStr>6) { scroll(); numStr--; } drawInit(numStr); IRData=-1; while(IRData != 96) { IRData=IRRead(); if (IRData != 96) { sprintf(initStr[numStr], "%s%d", initStr[numStr], IRData); drawInit(numStr); idLoc[ii]=idLoc[ii]*10+IRData; } } numStr++; } } void loop(void) { }
case не обязательно должен заканчиваться break -- может заканчиваться return, а может и не заканчиваться, если это предусмотрено логикой программы. Первым аргументом функции sprintf должен быть выделенный участок памяти, а Вы используете случайно попавшийся. Вот это и является причиной странности. Например, следующий код: Код (C++): sprintf(initStr[numStr], "%s%d", initStr[numStr], IRData); initStr[numStr] -- это участок памяти определённого размера, который был выделен компилятором. Где был выделен, только одному компилятору известно (хотя если дизассемблировать программу, то можно узнать). Вы же в него (участок памяти) пытаетесь "сфорамтировать" строку явно большего размера, что соответственно приводит к затиранию соседней памяти и, в полне вероятно, строк, которые храняться в этой памяти.
Я что-то в этом роду и подозревал, но ведь char* предполагает строку неопределённой длинны, и компилятор должен это учитывать. Или я что-то не так понимаю? Я на C что-либо серъёзное не писал уже лет 20, много забыл. Как мне с этой проблемой справиться? Причём, если я не использую sprintf, а просто присваиваю этой переменной строку произвольной длинны, то всё работает нормально.
char * -- это указатель на переменную типа char, т.е. на символ. В случае со строками -- это указатель на первый символ строки, в случае с массивами -- это указатель на первый элемент массива (хотя строка это и есть массив типа char). Компилятор знает только то, что (как уже сказал) 'char *' указывает на символ (в случае с AVR это один байт), а то что этот символ -- начало строки, которая имеет какую-то длину, знает только программист. Это не совсем корректное высказывание -- переменной присваивается не строка, а ссылка на первый символ строки, т.е. в случае с кодом: Код (C++): initStr[0]="Ввод первичных данных"; компилятор сначала выделит массив нужного размера в памяти и заполнит этот массив значениями "Ввод первичных данных\0" (\0 в конце строки добавит компилятор, чтобы показать конец массива - строки), а потом адрес первого байта присвоит переменной initStr[0]. После этого при любых операциях по доступу к памяти посредством переменной initStr[0] компилятор думает, что "программист знает что делает -- если надо выйти за пределы массива, не проблема выйдем и перезапишем данные, а то, что это приведёт к порче других переменных, мне (компилятору) по-барабану". Выделить массив нужной длины и указатель на этот массив передать в качестве первого аргумента функции sprintf. Если заранее длина массива не известна, то лучше воспользоваться функцией snprintf (вобще этой функцией лучше пользоваться всегда, вместо sprintf, т.к. она более безопасна), пример кода: Код (C++): char *buffer = (char *) malloc(64); // выделяем участок памяти предположительного размера int len = snprintf(buffer, 64, "%s%d", initStr[numStr], IRData); if (len > 64) { // выделенной памяти оказалось мало free(buffer); // освобождаем память buffer = (char *)malloc(len + 1); // выделяем участок памяти нужного размера snprintf(buffer, len + 1, "%s%d", initStr[numStr], IRData); } ..... free(buffer); // !!!!освобождаем память!!!! Вот только с динамическим выделением памяти (malloc/free) нужно быть осторожней и вроде как в AVR'ках это не приветствуется.