在 ESP8266 上读取 c 中的 const* 时崩溃
Crash when reading const* in c on ESP8266
我正在制作一个系统,通过 SoftwareSerial 从 Arduino Uno 读取传感器值并通过 MQTT 发布它。但是,我认为我面临的问题更普遍,我必须承认我是 c 的新手。
我正在读取数据,并将其拆分为在程序顶部定义的两个 const* 变量。
当我读回从串行连接解析的已保存 "data" 和 "topic" 变量时,我只得到垃圾输出,并且通常是重启设备的崩溃。
在read-from-serial函数中打印成功,但后面无法正确读取。它与数据的保存方式有关吗?我可以显式地为变量分配一些内存吗?
我使用的是 ESP8266 (ESP07) 芯片,具有较低的波特率和适当的电压供应。好像运行很好很稳定。
#include <StringSplitter.h>
#include <PubSubClient.h>
#include <ESP8266WiFi.h>
#include <time.h>
//const char* ssid = "xxxx";
//const char* password = "xxxx";
const char* ssid = "xxxx";
const char* password = "xxxx";
const char* mqttServer = "xxxx;
const int mqttPort = xxxx;
const char* mqttUser = "xxxx";
const char* mqttPassword = "xxxx";
int timezone = 1;
int dst = 0;
数据存储在这里:
char* data;
char* topic;
boolean newData = false;
boolean unpublishedData = false;
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin(19200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
configTime(timezone * 3600, dst * 0, "pool.ntp.org", "time.nist.gov");
client.setServer(mqttServer, mqttPort);
client.setCallback(callback);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("ESP8266Client", mqttUser, mqttPassword )) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
// wait and determine if we have a valid time from the network.
time_t now = time(nullptr);
Serial.print("Waiting for network time.");
while (now <= 1500000000) {
Serial.print(".");
delay(300); // allow a few seconds to connect to network time.
now = time(nullptr);
}
}
Serial.println("");
time_t now = time(nullptr);
Serial.println(ctime(&now));
String datatext = "val: ";
String timetext = ", time: ";
String dataToBeSent = "test";
String timeToBeSent = ctime(&now);
String publishString = datatext + dataToBeSent + timetext + timeToBeSent;
Serial.println("Attempting to publish: " + publishString);
client.publish("trykk/sensor0", (char*) publishString.c_str());
client.subscribe("trykk/sensor0");
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
Serial.println("-----------------------");
}
void loop() {
client.loop();
recvWithStartEndMarkers();
showNewData();
publishReceived();
}
void publishReceived() {
if (unpublishedData) {
Serial.println("Hello from inside the publisher loop!");
time_t now = time(nullptr);
char* timeNow = ctime(&now);
这里失败,正在读取数据:
char publishText[30]; //TODO: make it JSON
strcpy( publishText, data );
strcat( publishText, " " );
strcat( publishText, timeNow );
Serial.print("publishText: ");
Serial.println(publishText);
Serial.print("topic: ");
Serial.println(topic);
client.publish(topic, publishText);
client.subscribe(topic);
unpublishedData = false;
} else if (!data) {
Serial.println("No data saved to array.");
} else if (!topic) {
Serial.println("No topic saved to array.");
}
}
void recvWithStartEndMarkers() {
int numChars = 32;
char receivedChars[numChars];
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
if (Serial.available() > 0) {
Serial.println("Hello from inside the receive loop!");
delay(100);
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
Serial.println("Reading from data line.");
if (recvInProgress == true) {
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
Serial.println("Found the end marker.");
receivedChars[ndx] = '[=12=]'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
unpublishedData = true;
这部分将正确的值打印回给我:
//Split the string
Serial.print("ESP debug: read: ");
Serial.println(receivedChars);
const char s[2] = ":";
*data = strtok(receivedChars, s);
Serial.print(data);
Serial.print(" ");
*topic = strtok(NULL, s);
Serial.println(topic);
}
}
else if (rc == startMarker) {
recvInProgress = true;
Serial.println("Found start marker");
}
}
}
}
//This is gutted as it gave me problems reading the variables
void showNewData() {
if (newData == true) {
Serial.print("This just in ... ");
Serial.print("Topic: ");
Serial.print("stuff");
Serial.print(", data: ");
Serial.println("more stuff");
newData = false;
}
}
来自您的代码:
char* data;
...
*data = strtok(receivedChars, s);
strtok return a char*
但你做 *data = strtok(...)
而 data 本身就是一个(未初始化)char *
,这是不一致的,并且您首先 'chance' 发生崩溃,因为您在随机地址写入。
如果您没有崩溃并且您的程序可以继续 数据 不会自行修改并保持未初始化状态。
在
strcpy( publishText, data );
...
Serial.print(data);
当您使用 data 作为 char*
执行 Serial.print(data);
和 strcpy( publishText, data );
时,您从一个随机(当然是无效的)地址读取,产生你的崩溃。
要更正,只需将 *data = strtok(receivedChars, s);
替换为 data = strtok(receivedChars, s);
如 bruno 的回答所示,在修复了 strtok
的结果分配给 data
之后,还有另一个可能导致崩溃的错误。
您的函数 loop()
首先调用 recvWithStartEndMarkers()
,然后 publishReceived()
。
void loop() {
client.loop();
recvWithStartEndMarkers();
showNewData();
publishReceived();
}
在函数 recvWithStartEndMarkers
中,您将一些数据读入局部数组 receivedChars
,将其输入 strtok
并将从 strtok
返回的指针写入全局变量 data
.
void recvWithStartEndMarkers() {
int numChars = 32;
char receivedChars[numChars]; /* this is a local variable with automatic storage */
/* ... */
while (Serial.available() > 0 && newData == false) {
/* ... */
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
/* ... */
receivedChars[ndx] = '[=11=]'; // terminate the string
/* Now there is a terminated string in the local variable */
/* ... */
//Split the string
/* ... */
const char s[2] = ":";
data = strtok(receivedChars, s); /* strtok modifies the input in receivedChars and returns a pointer to parts of this array. */
/* ... */
}
离开函数后,receivedChars
的记忆不再有效。这意味着 data
将指向堆栈上的这个无效内存。
稍后您想在函数 publishReceived()
中访问全局变量 data
。访问此内存是未指定的行为。你可能仍然得到数据,你可能得到其他东西或者你的程序可能会崩溃。
void publishReceived() {
/* ... */
char publishText[30]; //TODO: make it JSON
strcpy( publishText, data ); /* This will try to copy whatever is now in the memory that was part of receivedChars inside recvWithStartEndMarkers() but may now contain something else, e.g. local data of function publishReceived(). */
/* ... */
要解决此问题,您可以在 recvWithStartEndMarkers()
:
中使用 strdup
data = strtok(receivedChars, s);
if(data != NULL) data = strdup(data);
那么当您不再需要数据或再次调用 recvWithStartEndMarkers()
之前,您必须 free(data)
某个地方。
或者使data
成为一个数组并在recvWithStartEndMarkers()
中使用strncpy
。
我正在制作一个系统,通过 SoftwareSerial 从 Arduino Uno 读取传感器值并通过 MQTT 发布它。但是,我认为我面临的问题更普遍,我必须承认我是 c 的新手。
我正在读取数据,并将其拆分为在程序顶部定义的两个 const* 变量。
当我读回从串行连接解析的已保存 "data" 和 "topic" 变量时,我只得到垃圾输出,并且通常是重启设备的崩溃。
在read-from-serial函数中打印成功,但后面无法正确读取。它与数据的保存方式有关吗?我可以显式地为变量分配一些内存吗?
我使用的是 ESP8266 (ESP07) 芯片,具有较低的波特率和适当的电压供应。好像运行很好很稳定。
#include <StringSplitter.h>
#include <PubSubClient.h>
#include <ESP8266WiFi.h>
#include <time.h>
//const char* ssid = "xxxx";
//const char* password = "xxxx";
const char* ssid = "xxxx";
const char* password = "xxxx";
const char* mqttServer = "xxxx;
const int mqttPort = xxxx;
const char* mqttUser = "xxxx";
const char* mqttPassword = "xxxx";
int timezone = 1;
int dst = 0;
数据存储在这里:
char* data;
char* topic;
boolean newData = false;
boolean unpublishedData = false;
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
Serial.begin(19200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
configTime(timezone * 3600, dst * 0, "pool.ntp.org", "time.nist.gov");
client.setServer(mqttServer, mqttPort);
client.setCallback(callback);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("ESP8266Client", mqttUser, mqttPassword )) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
// wait and determine if we have a valid time from the network.
time_t now = time(nullptr);
Serial.print("Waiting for network time.");
while (now <= 1500000000) {
Serial.print(".");
delay(300); // allow a few seconds to connect to network time.
now = time(nullptr);
}
}
Serial.println("");
time_t now = time(nullptr);
Serial.println(ctime(&now));
String datatext = "val: ";
String timetext = ", time: ";
String dataToBeSent = "test";
String timeToBeSent = ctime(&now);
String publishString = datatext + dataToBeSent + timetext + timeToBeSent;
Serial.println("Attempting to publish: " + publishString);
client.publish("trykk/sensor0", (char*) publishString.c_str());
client.subscribe("trykk/sensor0");
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
Serial.println("-----------------------");
}
void loop() {
client.loop();
recvWithStartEndMarkers();
showNewData();
publishReceived();
}
void publishReceived() {
if (unpublishedData) {
Serial.println("Hello from inside the publisher loop!");
time_t now = time(nullptr);
char* timeNow = ctime(&now);
这里失败,正在读取数据:
char publishText[30]; //TODO: make it JSON
strcpy( publishText, data );
strcat( publishText, " " );
strcat( publishText, timeNow );
Serial.print("publishText: ");
Serial.println(publishText);
Serial.print("topic: ");
Serial.println(topic);
client.publish(topic, publishText);
client.subscribe(topic);
unpublishedData = false;
} else if (!data) {
Serial.println("No data saved to array.");
} else if (!topic) {
Serial.println("No topic saved to array.");
}
}
void recvWithStartEndMarkers() {
int numChars = 32;
char receivedChars[numChars];
static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
if (Serial.available() > 0) {
Serial.println("Hello from inside the receive loop!");
delay(100);
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
Serial.println("Reading from data line.");
if (recvInProgress == true) {
if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
Serial.println("Found the end marker.");
receivedChars[ndx] = '[=12=]'; // terminate the string
recvInProgress = false;
ndx = 0;
newData = true;
unpublishedData = true;
这部分将正确的值打印回给我:
//Split the string
Serial.print("ESP debug: read: ");
Serial.println(receivedChars);
const char s[2] = ":";
*data = strtok(receivedChars, s);
Serial.print(data);
Serial.print(" ");
*topic = strtok(NULL, s);
Serial.println(topic);
}
}
else if (rc == startMarker) {
recvInProgress = true;
Serial.println("Found start marker");
}
}
}
}
//This is gutted as it gave me problems reading the variables
void showNewData() {
if (newData == true) {
Serial.print("This just in ... ");
Serial.print("Topic: ");
Serial.print("stuff");
Serial.print(", data: ");
Serial.println("more stuff");
newData = false;
}
}
来自您的代码:
char* data; ... *data = strtok(receivedChars, s);
strtok return a char*
但你做 *data = strtok(...)
而 data 本身就是一个(未初始化)char *
,这是不一致的,并且您首先 'chance' 发生崩溃,因为您在随机地址写入。
如果您没有崩溃并且您的程序可以继续 数据 不会自行修改并保持未初始化状态。
在
strcpy( publishText, data ); ... Serial.print(data);
当您使用 data 作为 char*
执行 Serial.print(data);
和 strcpy( publishText, data );
时,您从一个随机(当然是无效的)地址读取,产生你的崩溃。
要更正,只需将 *data = strtok(receivedChars, s);
替换为 data = strtok(receivedChars, s);
如 bruno 的回答所示,在修复了 strtok
的结果分配给 data
之后,还有另一个可能导致崩溃的错误。
您的函数 loop()
首先调用 recvWithStartEndMarkers()
,然后 publishReceived()
。
void loop() {
client.loop();
recvWithStartEndMarkers();
showNewData();
publishReceived();
}
在函数 recvWithStartEndMarkers
中,您将一些数据读入局部数组 receivedChars
,将其输入 strtok
并将从 strtok
返回的指针写入全局变量 data
.
void recvWithStartEndMarkers() {
int numChars = 32;
char receivedChars[numChars]; /* this is a local variable with automatic storage */
/* ... */
while (Serial.available() > 0 && newData == false) {
/* ... */
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
/* ... */
receivedChars[ndx] = '[=11=]'; // terminate the string
/* Now there is a terminated string in the local variable */
/* ... */
//Split the string
/* ... */
const char s[2] = ":";
data = strtok(receivedChars, s); /* strtok modifies the input in receivedChars and returns a pointer to parts of this array. */
/* ... */
}
离开函数后,receivedChars
的记忆不再有效。这意味着 data
将指向堆栈上的这个无效内存。
稍后您想在函数 publishReceived()
中访问全局变量 data
。访问此内存是未指定的行为。你可能仍然得到数据,你可能得到其他东西或者你的程序可能会崩溃。
void publishReceived() {
/* ... */
char publishText[30]; //TODO: make it JSON
strcpy( publishText, data ); /* This will try to copy whatever is now in the memory that was part of receivedChars inside recvWithStartEndMarkers() but may now contain something else, e.g. local data of function publishReceived(). */
/* ... */
要解决此问题,您可以在 recvWithStartEndMarkers()
:
strdup
data = strtok(receivedChars, s);
if(data != NULL) data = strdup(data);
那么当您不再需要数据或再次调用 recvWithStartEndMarkers()
之前,您必须 free(data)
某个地方。
或者使data
成为一个数组并在recvWithStartEndMarkers()
中使用strncpy
。