Может ли strcpy редактировать адреса памяти на Arduino? - PullRequest
0 голосов
/ 18 января 2019

Я анализирую массивы символов из последовательной шины и копирую их содержимое в глобальные массивы для обработки в других функциях. Я замечаю странное поведение при многократном использовании strcpy() и strtok(). С Arduino Mega могут ли повторные вызовы на strcpy испортить адрес памяти?

Это для низкоуровневого прибора, в котором Arduino выступает в роли локального микроконтроллера, принимающего команды через последовательный порт. Я сделал несколько разных способов инициализации глобальных массивов, в том числе:

//char testDate = "YYmmDD"; //failed
//char testDate[6] = "";
//char testDate[6] = "YYmmDD";
//char testDate[6] = {'Y', 'Y', 'm', 'm', 'D', 'D'};
//char testTime = "HHMMSS"; //failed
//char testTime[6] = "";
//char testTime[6] = "HHMMSS";
//char testTime[6] = {'H', 'H', 'M', 'M', 'S', 'S'};
//char logfile[24] = "BATCH_YYmmDD_HHMMSS$.txt"; // 20 + 4, exact size
//char logfile[24] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};

//char testDate[6] = ""; // Null
//char testTime[6] = ""; // Null
char testDate[6] = {'Y', 'Y', 'm', 'm', 'D', 'D'};
char testTime[6] = {'H', 'H', 'M', 'M', 'S', 'S'}; // is appending?
char logfile[24] = ""; // Null

Демонстрационный код в простейшем виде;

#include <String.h>

char gva_logfile[24] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
char gva_testDate[6] = {'Y', 'Y', 'm', 'm', 'D', 'D'}; // is appending?
char gva_testTime[6] = {'H', 'H', 'M', 'M', 'S', 'S'}; 

char lva_testDate[6];

//String y = "BATCH|190117;151442$";
//int lan = y.length(); // should be 20

char x[20] = "BATCH|190117;151442$";
int lan = strlen(x);
//y.toCharArray(x, lan);
//strcpy(x, y);
//y.toCharArray(x, 20);

//strcpy(x, "BATCH|190117;151442$");

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  Serial.print("<");
  for(int i=0; i<6; i++){
    Serial.print(gva_testDate[i]); // works
    //Serial.print(gva_testTime[i]); // works
  }
  Serial.println(">"); 
}

void loop() {
  // put your main code here, to run repeatedly:
  char tele[6] = {' ', ' ', ' ', ' ', ' ', ' '};
  while(1){
    char flarb[lan];
    strcpy(flarb, x);
    //Serial.println(flarb);

    if(strstr(flarb, "BATCH|")){
      char * strtokIndx;
      strtokIndx = strtok(x, "|");
      //strcpy(tele, strtokIndx);     // did nothing?
      strtokIndx = strtok(NULL, ";");
      strcpy(gva_testDate, strtokIndx); // missing?

      Serial.println(gva_testDate); // Not missing
      for(int i=0; i<6; i++){
        lva_testDate[i] = gva_testDate[i];  
      }      

      strtokIndx = strtok(NULL, "$");
      strcpy(gva_testTime, strtokIndx); // is fine...

      Serial.println(gva_testDate); // MISSING
      Serial.println(lva_testDate);

      if(strstr(gva_testDate, "YYmmDD")!=NULL || strstr(gva_testTime, "HHMMSS")!=NULL){
          //if((gva_testDate == "YYmmDD") || (gva_testTime == "HHMMSS")){  
          char io[28]; // 16 + 2*6
          sprintf(io, "063 ERROR: %s,%s", gva_testDate, gva_testTime);
          //logArdData(io);
          Serial.print("<");
          Serial.print(io);
          Serial.println(">");
          Serial.flush();
        }
        //else if((strstr(gva_testDate, "YYmmDD") && strstr(gva_testTime, "HHMMSS"))==NULL){
        else if((strstr(gva_testDate, "YYmmDD")==NULL && strstr(gva_testTime, "HHMMSS")==NULL)){  
          //else if((gva_testDate != "YYmmDD") && (gva_testTime != "HHMMSS")){  
          char io[26]; // 14 + 2*6

          //sendArdData(gva_testDate); // is combinined?
          //sendArdData(gva_testTime); // still itself

          sprintf(io, "Assigned %s,%s", gva_testDate, gva_testTime); // is combining testDate and testTime, then printing testTime?
          Serial.print("<");
          Serial.print(io);
          Serial.println(">");
          //sendArdData(io);
          //logArdData(io);
          Serial.flush();        
        }
    }
  }
}

Когда я пытаюсь повторно напечатать сохраненное значение gva_testDate после присвоения gva_testTime, каким-то образом gva_testDate стало NULL вместо 190117. Я ожидал, что gva_testDate сохранит свое вновь присвоенное значение, однако это не так.

Справочные страницы C ++ для strcpy() http://www.cplusplus.com/reference/cstring/strcpy/?kw=strcpy и strtok() http://www.cplusplus.com/reference/cstring/strtok/ не содержат упоминаний о проблемах / предупреждениях о повреждении памяти, поэтому я не ожидал бы, что повторные вызовы изменят исходная строка x или проанализированные массивы символов gva_testDate & gva_testTime.

Ответы [ 2 ]

0 голосов
/ 20 января 2019

Я сделал тест и точно выяснил, что здесь происходит.

Это проблема со strtok, а также тот факт, что вы предоставляете символьные массивы всем этим строковым функциям без пробела для ограничителя строки, то есть '0'.

Это тестовая функция, которую я написал на компьютере для проверки

void printAllObjects(int location,char *x, char* strtokIndx, char *gva_testDate,char *gva_testTime)
{
    printf("At location %d\n x pointer %d, string %s\n"
            " strtokIndx pointer %d, string %s\n "
            "gva_testDate pointer %d, string %s\n "
            "gva_testTime pointer %d, string %s\n ",location,x,x,strtokIndx,strtokIndx,gva_testDate,gva_testDate,gva_testTime,gva_testTime);
}

Затем я скопировал весь ваш код arduino на мой компьютер, закомментировал все последовательные команды, добавил заголовок stdio и использовал вышеуказанную функцию в нескольких местах.

#include <cstdlib>
#include <String.h>
#include <stdio.h>

using namespace std;

/*
 * 
 */

char gva_logfile[24] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
char gva_testDate[6] = {'Y', 'Y', 'm', 'm', 'D', 'D'}; // is appending?
char gva_testTime[6] = {'H', 'H', 'M', 'M', 'S', 'S'}; 

char lva_testDate[6];

//String y = "BATCH|190117;151442$";
//int lan = y.length(); // should be 20

char x[] = "BATCH|190117;151442$";
int lan = strlen(x);
//y.toCharArray(x, lan);
//strcpy(x, y);
//y.toCharArray(x, 20);

//strcpy(x, "BATCH|190117;151442$");


void printAllObjects(int location,char *x, char* strtokIndx, char *gva_testDate,char *gva_testTime)
{
    printf("At location %d\n x pointer %d, string %s\n"
            " strtokIndx pointer %d, string %s\n "
            "gva_testDate pointer %d, string %s\n "
            "gva_testTime pointer %d, string %s\n ",location,x,x,strtokIndx,strtokIndx,gva_testDate,gva_testDate,gva_testTime,gva_testTime);
}

void setup() {
  // put your setup code here, to run once:
//  Serial.begin(9600);
//
//  Serial.print("<");
//  for(int i=0; i<6; i++){
//    Serial.print(gva_testDate[i]); // works
//    //Serial.print(gva_testTime[i]); // works
//  }
//  Serial.println(">"); 
}



void loop() {
  // put your main code here, to run repeatedly:
  char tele[6] = {' ', ' ', ' ', ' ', ' ', ' '};
  while(1){
    char flarb[lan];
    strcpy(flarb, x);
    //Serial.println(flarb);

    if(strstr(flarb, "BATCH|")){
      char * strtokIndx;
      printAllObjects(1,x,strtokIndx,gva_testDate,gva_testTime);
      strtokIndx = strtok(x, "|");
      printAllObjects(2,x,strtokIndx,gva_testDate,gva_testTime);
      //strcpy(tele, strtokIndx);     // did nothing?
      strtokIndx = strtok(NULL, ";");
      printAllObjects(3,x,strtokIndx,gva_testDate,gva_testTime);
      strcpy(gva_testDate, strtokIndx); // missing?
      printAllObjects(4,x,strtokIndx,gva_testDate,gva_testTime);

//      Serial.println(gva_testDate); // Not missing
      for(int i=0; i<6; i++){
        lva_testDate[i] = gva_testDate[i];  
      }      

      strtokIndx = strtok(NULL, "$");
      printAllObjects(5,x,strtokIndx,gva_testDate,gva_testTime);
      strcpy(gva_testTime, strtokIndx); // is fine...
      printAllObjects(6,x,strtokIndx,gva_testDate,gva_testTime);

//      Serial.println(gva_testDate); // MISSING
//      Serial.println(lva_testDate);

      if(strstr(gva_testDate, "YYmmDD")!=NULL || strstr(gva_testTime, "HHMMSS")!=NULL){
          //if((gva_testDate == "YYmmDD") || (gva_testTime == "HHMMSS")){  
          char io[28]; // 16 + 2*6
          sprintf(io, "063 ERROR: %s,%s", gva_testDate, gva_testTime);
          //logArdData(io);
//          Serial.print("<");
//          Serial.print(io);
//          Serial.println(">");
//          Serial.flush();
        }
        //else if((strstr(gva_testDate, "YYmmDD") && strstr(gva_testTime, "HHMMSS"))==NULL){
        else if((strstr(gva_testDate, "YYmmDD")==NULL && strstr(gva_testTime, "HHMMSS")==NULL)){  
          //else if((gva_testDate != "YYmmDD") && (gva_testTime != "HHMMSS")){  
          char io[26]; // 14 + 2*6

          //sendArdData(gva_testDate); // is combinined?
          //sendArdData(gva_testTime); // still itself

          sprintf(io, "Assigned %s,%s", gva_testDate, gva_testTime); // is combining testDate and testTime, then printing testTime?
//          Serial.print("<");
//          Serial.print(io);
//          Serial.println(">");
          //sendArdData(io);
          //logArdData(io);
//          Serial.flush();        
        }
    }
  }
}
int main(int argc, char** argv) {
    setup();
    printf("Entering loop\n");
    loop();
    return 0;
}

Это вывод, который я получил

Entering loop
At location 1
 x pointer 199946368, string BATCH|190117;151442$
 strtokIndx pointer 0, string (null)
 gva_testDate pointer 199946344, string YYmmDDHHMMSS
 gva_testTime pointer 199946350, string HHMMSS
 At location 2
 x pointer 199946368, string BATCH
 strtokIndx pointer 199946368, string BATCH
 gva_testDate pointer 199946344, string YYmmDDHHMMSS
 gva_testTime pointer 199946350, string HHMMSS
 At location 3
 x pointer 199946368, string BATCH
 strtokIndx pointer 199946374, string 190117
 gva_testDate pointer 199946344, string YYmmDDHHMMSS
 gva_testTime pointer 199946350, string HHMMSS
 At location 4
 x pointer 199946368, string BATCH
 strtokIndx pointer 199946374, string 190117
 gva_testDate pointer 199946344, string 190117
 gva_testTime pointer 199946350, string 
 At location 5
 x pointer 199946368, string BATCH
 strtokIndx pointer 199946381, string 151442
 gva_testDate pointer 199946344, string 190117
 gva_testTime pointer 199946350, string 
 At location 6
 x pointer 199946368, string BATCH
 strtokIndx pointer 199946381, string 151442
 gva_testDate pointer 199946344, string 190117151442
 gva_testTime pointer 199946350, string 151442

На основании этих данных это мой вывод.

Функция strtok изменяет исходную строку и заменяет поисковый символ на ноль, затем копирует строку в новый индекс и возвращает индекс.

strcpy ничего не делает с вашим кодом, вы записали значения буфера таким образом, что кажется, что strcpy делает неприятные вещи, но он просто делает то, что должен делать. копирование строк, пока он не найдет ноль. В большинстве случаев это будет много для вас, но не в этом конкретном случае.

Как видно из результатов, gva_testDate и gva_testTime имеют разность указателей, равную 6, поэтому при заполнении gva_testTime, если вы используете gva_testDate в качестве строки, ноль отображается только в конце 12 символов.

Кроме того, lva_testDate должен быть для вас буфером, вам нужно дать ему значение для начала, или он станет нулевым буфером и может закончить тем, что изменил разные вещи в зависимости от того, к чему скомпилирован код.

Чтобы решить эту проблему, я инициализировал все ваши переменные, поэтому вывод выводится ниже этого.

char gva_testDate[] = "YYmmDD"; // is appending?
char gva_testTime[] = "HHMMSS";

char lva_testDate[20];

//String y = "BATCH|190117;151442$";
//int lan = y.length(); // should be 20

char x[] = "BATCH|190117;151442$";
int lan = strlen(x);

выход

Entering loop
At location 1
 x pointer 107077760, string BATCH|190117;151442$
 strtokIndx pointer 0, string (null)
 gva_testDate pointer 107077736, string YYmmDD
 gva_testTime pointer 107077743, string HHMMSS
 At location 2
 x pointer 107077760, string BATCH
 strtokIndx pointer 107077760, string BATCH
 gva_testDate pointer 107077736, string YYmmDD
 gva_testTime pointer 107077743, string HHMMSS
 At location 3
 x pointer 107077760, string BATCH
 strtokIndx pointer 107077766, string 190117
 gva_testDate pointer 107077736, string YYmmDD
 gva_testTime pointer 107077743, string HHMMSS
 At location 4
 x pointer 107077760, string BATCH
 strtokIndx pointer 107077766, string 190117
 gva_testDate pointer 107077736, string 190117
 gva_testTime pointer 107077743, string HHMMSS
 At location 5
 x pointer 107077760, string BATCH
 strtokIndx pointer 107077773, string 151442
 gva_testDate pointer 107077736, string 190117
 gva_testTime pointer 107077743, string HHMMSS
 At location 6
 x pointer 107077760, string BATCH
 strtokIndx pointer 107077773, string 151442
 gva_testDate pointer 107077736, string 190117
 gva_testTime pointer 107077743, string 151442

Надеюсь, это поможет.

0 голосов
/ 18 января 2019

Как предположили melpomene и alain в комментариях, ваши проблемы связаны с отсутствием NULL-терминаторов в ваших строках. Из вашего комментария, я полагаю, вы можете использовать класс C ++ String, который хранит свойства, которые сообщают вам размер строки сразу. Однако в C строки - это просто массивы символов в памяти, и не существует непосредственного способа определить их длину, кроме подсчета символов, пока мы не достигнем значения NULL (0x00).

Вот почему strcpy делает неприятные вещи в вашем коде, когда копирует строки, он начинает копировать байты в памяти, пока не достигнет нулевого символа где-нибудь и не вернется. В вашем случае он не найдет нулевой символ сразу и продолжит копирование, пока не найдет его в вашей памяти и не подпишет другие переменные.

Я вижу в вашем коде, что некоторые из ваших строк предназначены для фиксированного размера, и нулевой символ может быть избыточным. В этом случае, когда вы знаете размер строки (либо потому, что вы жестко ее кодировали, либо сохраняете ее в некоторой переменной), вам нужна функция memcpy. Он просто скопирует установленное количество байтов с одного адреса на другой, не полагаясь на нулевые терминаторы. НО Serial.print по-прежнему будет ожидать, что его будут печатать нулевые терминаторы, поэтому вам также придется это изменить.

Самое простое решение - просто оставить больше места в строках. Например, если вы хотите создать строку "abc" , вам следует использовать char myString[4] = "abc". Компилятор автоматически заполняет память, начиная с myString, 0x61 0x62 0x63 0x00, поскольку компилятор знает, что в C строки (отмеченные ") должны заканчиваться нулем, вы должны предоставить только один дополнительный байт для нулевого значения. терминатор. Если ваша строка будет иметь переменную длину, вы должны предоставить достаточно памяти для сценария наихудшего случая плюс нулевой терминатор, поэтому для вашей переменной x может быть использовано более 21 байта. Вы не можете изменить этот размер позже, но то, что определяет длину вашей строки, это нулевой терминатор, а не объем памяти, который он зарезервировал.

Однако строки этой формы:

char gva_testTime[6] = {'H', 'H', 'M', 'M', 'S', 'S'};

будет не включать нулевой символ, потому что вместо строки вы объявили массив символов. если вы хотите использовать strcpy или strtok в этой переменной, вам нужно будет включить нулевой терминатор самостоятельно:

char gva_testTime[7] = {'H', 'H', 'M', 'M', 'S', 'S', 0};

Или объявить его в виде строки:

char gva_testTime[7] = "HHMMSS";

В общем, просмотрите все ваши объявления строк, чтобы включить в них один дополнительный байт для нулевых терминаторов, и убедитесь, что способ, которым вы объявляете его, фактически будет содержать нулевой терминатор.

...