I cannot connect to Wi-fi on my ESP32 DEV-KIT; I have a String which contains ssid and password, but the WIFI.begin() function accepts only const char*, and I don't know how to convert String to const char*. I have tried the c_str() function, but the result I am getting is != (not equal) to the actual ssid.
String ssid = "DIR-615A";
char ssidR;
const char* ssidR2 = "DIR-615A";
void setup() {
ssidR = ssid.c_str();
Serial.println(ssidR);
Serial.println(ssidR2);
Serial.println(ssidR == ssidR2); //this returns 0
}
Both ssidR and ssidR2 look identical when printing them, but when I use ssidR to connect to wi-fi, it is not working.
You can use toCharArray() to convert the String object into an array of chars.
Also, you have to use strcmp() to compare two strings, otherwise you're literally just comparing the address in memory where the data is kept.
To use it:
char ssid_char[100]; /* or however long your SSID is going to be */
ssid.toCharArray(ssid_char, ssid.length());
wifi.begin(ssid_char);
The reason is that you need space to store the content of the string somewhere. Your ssidR variable was just a pointer to memory, but there was no memory to store the actual string. By using a static array, the memory is reserved for the toCharArray() call to fill.
I was going to try to put this as a comment, but it's unwieldy there. It's kind of screwed up as an answer, because your question seems to be predicated on an misunderstandings of what to expect from == and const char * and what can be done with a single char, but it does at least address what's been said.
Regarding /*char*/ ssidR = ssid.c_str(); and ssidR == ssidR2
I addressed this part in a comment, but since as though I'm making an answer now I'll pull it into here. I'm not repeating this just to beat a dead horse.
You are attempting to assign a pointer to const char to a single char. Basically you're trying to cram 32bit (in this case) address of something into a character and then you're trying to compare that with const char * again:
Serial.println(ssidR == ssidR2); //this returns 0
Right, it would do. Without splitting hairs about the language standard and undefined behaviour on overflow, etc. what you're doing here is comparing the low byte of the address of the buffer maintained by your String object with the full address of your string literal "DIR-615A" and they're not equal.
begin() and const char *
From your comment on the question
i dont know why the guy who wrote th elibrary decided to make it accept only char pointers.
An entire answer could be written on the decision to just support const char *; There are reasons, though some maybe relevant more historically and may persist more out of habit than anything else. If you want an example of that sort of thing you can look at open() for fstream in C++ prior to C++17; it accepted const char * but not std::string which is standard C++'s counterpart to the Arduino String type.
The main thing I'd impress on you is that it accepting const char * is not actually a problem for you at all. There's absolutely nothing wrong with calling begin with ssid.c_str() and your WiFi connectivity problems don't have anything to do with their decision.
NOT Comparing c-strings
== on pointers compares the pointers themselves and not the things being pointed-to. In the case of const char *, the compiler couldn't generally know what to compare once it followed the pointer; at the time == is evaluated the compiler can't reliable know that these pointers point at null terminated strings and not some other sequence of zero or more chars.
The C and C++ languages allow for string literals to be overlapped in memory, that is to not have distinct addresses, which can lead you to the false sense that you've used == in a way that it's actually comparing the strings. "yes" == "yes" will usually evaluate to true, because typical compilers will reuse the first instance of the literal for the second, and so they're in the same memory location, so == appears to work when it in fact didn't. There's no requirement that the reuse the same memory. And in this case: char no[3] = "no"; if (no == "no") { will be false. Because the no char array is a distinct object, nothing else has its address, including a string literal you might try to compare it to which has the same contents.
Comparing c-strings
What you seem to want to do to convince yourself that .c_str() yields what you want can be done in a pile of ways. The traditional way (from C) to compare null-terminated c-strings is with the strcmp function:
String ssid_via_str_obj = "DIR-615A";
const char *ssid_via_char_ptr = "DIR-615A";
// ...
if (strcmp(ssid_via_str_obj.c_str(), ssid_via_char_ptr) == 0) {
// note strcmp returns zero for equality,
// numbers above and below zero are used to represent greater and lessor.
Serial.println("Equal");
} else {
Serial.println("Unequal");
}
There's also an endless pile of ways you could do this with the Arduino String, and c++ std::string and std::string_view classes you have available to you.
Related
There is a function which sends data to the server:
int send(
_In_ SOCKET s,
_In_ const char *buf,
_In_ int len,
_In_ int flags
);
Providing length seems to me a little bit weird. I need to write a function, sending a line to the server and wrapping this one such that we don't have to provide length explicitly. I'm a Java-developer and in Java we could just invoke String::length() method, but now we're not in Java. How can I do that, unless providing length as a template parameter? For instance:
void sendLine(SOCKET s, const char *buf)
{
}
Is it possible to implement such a function?
Use std string:
void sendLine(SOCKET s, const std::string& buf) {
send (s, buf.c_str(), buf.size()+1, 0); //+1 will also transmit terminating \0.
}
On a side note: your wrapper function ignores the return value and doesn't take any flags.
you can retrieve the length of C-string by using strlen(const char*) function.
make sure all the strings are null terminated and keep in mind that null-termination (the length grows by 1)
Edit: My answer originally only mentioned std::string. I've now also added std::vector<char> to account for situations where send is not used for strictly textual data.
First of all, you absolutely need a C++ book. You are looking for either the std::string class or for std::vector<char>, both of which are fundamental elements of the language.
Your question is a bit like asking, in Java, how to avoid char[] because you never heard of java.lang.String, or how to avoid arrays in general because you never heard of java.util.ArrayList.
For the first part of this answer, let's assume you are dealing with just text output here, i.e. with output where a char is really meant to be a text character. That's the std::string use case.
Providing lenght seems to me a little bit wierd.
That's the way strings work in C. A C string is really a pointer to a memory location where characters are stored. Normally, C strings are null-terminated. This means that the last character stored for the string is '\0'. It means "the string stops here, and if you move further, you enter illegal territory".
Here is a C-style example:
#include <string.h>
#include <stdio.h>
void f(char const* s)
{
int l = strlen(s); // l = 3
printf(s); // prints "foo"
}
int main()
{
char* test = new char[4]; // avoid new[] in real programs
test[0] = 'f';
test[1] = 'o';
test[2] = 'o';
test[3] = '\0';
f(test);
delete[] test;
}
strlen just counts all characters at the specified position in memory until it finds '\0'. printf just writes all characters at the specified position in memory until it finds '\0'.
So far, so good. Now what happens if someone forgets about the null terminator?
char* test = new char[3]; // don't do this at home, please
test[0] = 'f';
test[1] = 'o';
test[2] = 'o';
f(test); // uh-oh, there is no null terminator...
The result will be undefined behaviour. strlen will keep looking for '\0'. So will printf. The functions will try to read memory they are not supposed to. The program is allowed to do anything, including crashing. The evil thing is that most likely, nothing will happen for a while because a '\0' just happens to be stored there in memory, until one day you are not so lucky anymore.
That's why C functions are sometimes made safer by requiring you to explicitly specify the number of characters. Your send is such a function. It works fine even without null-terminated strings.
So much for C strings. And now please don't use them in your C++ code. Use std::string. It is designed to be compatible with C functions by providing the c_str() member function, which returns a null-terminated char const * pointing to the contents of the string, and it of course has a size() member function to tell you the number of characters without the null-terminated character (e.g. for a std::string representing the word "foo", size() would be 3, not 4, and 3 is also what a C function like yours would probably expect, but you have to look at the documentation of the function to find out whether it needs the number of visible characters or number of elements in memory).
In fact, with std::string you can just forget about the whole null-termination business. Everything is nicely automated. std::string is exactly as easy and safe to use as java.lang.String.
Your sendLine should thus become:
void sendLine(SOCKET s, std::string const& line)
{
send(s, line.c_str(), line.size());
}
(Passing a std::string by const& is the normal way of passing big objects in C++. It's just for performance, but it's such a widely-used convention that your code would look strange if you just passed std::string.)
How can I do that, unless providing lenght as a template parameter?
This is a misunderstanding of how templates work. With a template, the length would have to be known at compile time. That's certainly not what you intended.
Now, for the second part of the answer, perhaps you aren't really dealing with text here. It's unlikely, as the name "sendLine" in your example sounds very much like text, but perhaps you are dealing with raw data, and a char in your output does not represent a text character but just a value to be interpreted as something completely different, such as the contents of an image file.
In that case, std::string is a poor choice. Your output could contain '\0' characters that do not have the meaning of "data ends here", but which are part of the normal contents. In other words, you don't really have strings anymore, you have a range of char elements in which '\0' has no special meaning.
For this situation, C++ offers the std::vector template, which you can use as std::vector<char>. It is also designed to be usable with C functions by providing a member function that returns a char pointer. Here's an example:
void sendLine(SOCKET s, std::vector<char> const& data)
{
send(s, &data[0], data.size());
}
(The unusual &data[0] syntax means "pointer to the first element of the encapsulated data. C++11 has nicer-to-read ways of doing this, but &data[0] also works in older versions of C++.)
Things to keep in mind:
std::string is like String in Java.
std::vector is like ArrayList in Java.
std::string is for a range of char with the meaning of text, std::vector<char> is for a range of char with the meaning of raw data.
std::string and std::vector are designed to work together with C APIs.
Do not use new[] in C++.
Understand the null termination of C strings.
I saw this example:
const char* SayHi() { return "Hi"; }
And it works fine, but if I try to remove the pointer it doesn't work and I can't figure
out why.
const char SayHi() { return "Hi"; } \\Pointer removed
It works if I assign it a single character like this:
const char SayHi() { return 'H'; } \\Pointer removed and only 1 character
But I don't know what makes it work exactly. Why would a pointer be able to hold more than one character? Isn't a pointer just a variable that points to another one? What does this point to?
That is because a char is by definition a single character (like in your 3rd case). If you want a string, you can either use a array of chars which decays to const char* (like in your first case) or, the C++ way, use std::string.
Here you can read more about the "array decaying to pointer" thing.
You are correct that a pointer is just a variable that points somewhere -- in this case it points to a string of characters somewhere in memory. By convention, strings (arrays of char) end with a null character (0), so operations like strlen can terminate safely without overflowing a buffer.
As for where that particular pointer (in your first example) points to, it is pointing to the string literal "Hi" (with a null terminator at the end added by the compiler). That location is platform-dependent and is answered here.
It is also better practice to use std::string in C++ than plain C arrays of characters.
I am new to C++, and haven't quite grasped all the concepts yet, so i am perplexed at why this function does not work. I am currently not at home, so i cannot post the compiler error just yet, i will do it as soon as i get home.
Here is the function.
const char * ConvertToChar(std::string input1, std::string input2) {
// Create a string that you want converted
std::stringstream ss;
// Streams the two strings together
ss << input1 << input2;
// outputs it into a string
std::string msg = ss.str();
//Creating the character the string will go in; be sure it is large enough so you don't overflow the array
cont char * cstr[80];
//Copies the string into the char array. Thus allowing it to be used elsewhere.
strcpy(cstr, msg.c_str());
return * cstr;
}
It is made to concatenate and convert two strings together to return a const char *. That is because the function i want to use it with requires a const char pointer to be passed through.
The code returns a pointer to a local (stack) variable. When the caller gets this pointer that local variable doesn't exist any more. This is often called dangling reference.
If you want to convert std::string to a c-style string use std::string::c_str().
So, to concatenate two strings and get a c-style string do:
std::string input1 = ...;
std::string input2 = ...;
// concatenate
std::string s = input1 + input2;
// get a c-style string
char const* cstr = s.c_str();
// cstr becomes invalid when s is changed or destroyed
Without knowing what the error is, it's hard to say, but this
line:
const char* cstr[80];
seems wrong: it creates an array of 80 pointers; when it
implicitly converts to a pointer, the type will be char
const**, which should give an error when it is passed as an
argument to strcpy, and the dereference in the return
statement is the same as if you wrote cstr[0], and returns the
first pointer in the array—since the contents of the array
have never been initialized, this is undefined behavior.
Before you go any further, you have to define what the function
should return—not only its type, but where the pointed to
memory will reside. There are three possible solutions to this:
Use a local static for the buffer:
This solution was
frequently used in early C, and is still present in a number of
functions in the C library. It has two major defects: 1)
successive calls will overwrite the results, so the client code
must make its own copy before calling the function again, and 2)
it isn't thread safe. (The second issue can be avoided by using
thread local storage.) In cases like yours, it also has the
problem that the buffer must be big enough for the data, which
probably requires dynamic allocation, which adds to the
complexity.
Return a pointer to dynamically allocated memory:
This works well in theory, but requires the client code to free
the memory. This must be rigorously documented, and is
extremely error prone.
Require the client code to provide the buffer:
This is probably the best solution in modern code, but it does
mean that you need extra parameters for the address and the
length of the buffer.
In addition to this: there's no need to use std::ostringstream
if all you're doing is concatenating; just add the two strings.
Whatever solution you use, verify that the results will fit.
I like "reinventing the wheel" for learning purposes, so I'm working on a container class for strings. Will using the NULL character as an array terminator (i.e., the last value in the array will be NULL) cause interference with the null-terminated strings?
I think it would only be an issue if an empty string is added, but I might be missing something.
EDIT: This is in C++.
"" is the empty string in C and C++, not NULL. Note that "" has exactly one element (instead of zero), meaning it is equivalent to {'\0'} as an array of char.
char const *notastring = NULL;
char const *emptystring = "";
emptystring[0] == '\0'; // true
notastring[0] == '\0'; // crashes
No, it won't, because you won't be storing in an array of char, you'll be storing in an array of char*.
char const* strings[] = {
"WTF"
, "Am"
, "I"
, "Using"
, "Char"
, "Arrays?!"
, 0
};
It depends on what kind of string you're storing.
If you're storing C-style strings, which are basically just pointers to character arrays (char*), there's a difference between a NULL pointer value, and an empty string. The former means the pointer is ‘empty’, the latter means the pointer points to an array that contains a single item with character value 0 ('\0'). So the pointer still has a value, and testing it (if (foo[3])) will work as expected.
If what you're storing are C++ standard library strings of type string, then there is no NULL value. That's because there is no pointer, and the string type is treated as a single value. (Whereas a pointer is technically not, but can be seen as a reference.)
I think you are confused. While C-strings are "null terminated", there is no "NULL" character. NULL is a name for a null pointer. The terminator for a C-string is a null character, i.e. a byte with a value of zero. In ASCII, this byte is (somewhat confusingly) named NUL.
Suppose your class contains an array of char that is used to store the string data. You do not need to "mark the end of the array"; the array has a specific size that is set at compile-time. You do need to know how much of that space is actually being used; the null-terminator on the string data accomplishes that for you - but you can get better performance by actually remembering the length. Also, a "string" class with a statically-sized char buffer is not very useful at all, because that buffer size is an upper limit on the length of strings you can have.
So a better string class would contain a pointer of type char*, which points to a dynamically allocated (via new[]) array of char s. Again, it makes no sense to "mark the end of the array", but you will want to remember both the length of the string (i.e. the amount of space being used) and the size of the allocation (i.e. the amount of space that may be used before you have to re-allocate).
When you are copying from std::string, use the iterators begin(), end() and you don't have to worry about the NULL - in reality, the NULL is only present if you call c_str() (in which case the block of memory this points to will have a NULL to terminate the string.) If you want to memcpy use the data() method.
Why don't you follow the pattern used by vector - store the number of elements within your container class, then you know always how many values there are in it:
vector<string> myVector;
size_t elements(myVector.size());
Instantiating a string with x where const char* x = 0; can be problematic. See this code in Visual C++ STL that gets called when you do this:
_Myt& assign(const _Elem *_Ptr)
{ // assign [_Ptr, <null>)
_DEBUG_POINTER(_Ptr);
return (assign(_Ptr, _Traits::length(_Ptr)));
}
static size_t __CLRCALL_OR_CDECL length(const _Elem *_First)
{ // find length of null-terminated string
return (_CSTD strlen(_First));
}
#include "Maxmp_crafts_fine_wheels.h"
MaxpmContaner maxpm;
maxpm.add("Hello");
maxpm.add(""); // uh oh, adding an empty string; should I worry?
maxpm.add(0);
At this point, as a user of MaxpmContainer who had not read your documentation, I would expect the following:
strcmp(maxpm[0],"Hello") == 0;
*maxpm[1] == 0;
maxpm[2] == 0;
Interference between the zero terminator at position two and the empty string at position one is avoided by means of the "interpret this as a memory address" operator *. Position one will not be zero; it will be an integer, which if you interpret it as a memory address, will turn out to be zero. Position two will be zero, which, if you interpret it as a memory address, will turn out to be an abrupt disorderly exit from your program.
I made a function like this:
bool IsSameString(char* p1, char* p2)
{
return 0 == strcmp(p1, p2);
}
The problem is that sometimes, by mistake, arguments are passed which are not strings (meaning that p1 or p2 is not terminated with a null character).
Then, strcmp continues comparing until it reaches non-accessible memory and crashes.
Is there a safe version of strcmp? Or can I tell whether p1 (and p2) is a string or not in a safe manner?
No, there's no (standard) way to tell whether a char * actually points to valid memory.
In your situation, it is better to use std::string rather than char *s for all your strings, along with the overloaded == operator. If you do this, the compiler would enforce type safety.
EDIT: As per the comments below if you find yourself in a situation where you sometimes pass char *s that may or may not be valid strings to functions that expect null-terminated strings then something is fundamentally wrong with your approach, so basically
#janm's answer below.
In some cases std::strncmp can solve your problem:
int strncmp ( const char * str1, const char * str2, size_t num );
It compares up to num characters of the C string str1 to those of the C string str2.
Also, take a look, what the US DHS National Cyber Security Division recommends on this matter:
Ensure that strings are null terminated before passing into strcmp. This can be enforced by always placing a \0 in the last allocated byte of the buffer.
char str1[] ="something";
char str2[] = "another thing";
/* In this case we know strings are null terminated. Pretend we don't. */
str1[sizeof(str1)-1] = '\0';
str2[sizeof(str2)-1] = '\0';
/* Now the following is safe. */
if (strcmp(str1, str2)) { /* do something */ } else { /* do something else */ }
If you are passing strings to strcmp() that are not null terminated you have already lost. The fact that you have a string that is not null terminated (but should be) indicates that you have deeper issues in your code. You cannot change strcmp() to safely deal with this problem.
You should be writing your code so that can never happen. Start by using the string class. At the boundaries where you take data into your code you need to make sure you deal with the exceptional cases; if you get too much data you need to Do The Right Thing. That does not involve running off the end of your buffer. If you must perform I/O into a C style buffer, use functions where you specify the length of the buffer and detect and deal with cases where the buffer is not large enough at that point.
There's no cure for this that is portable. The convention states that there's an extra character holding a null character that belongs to the same correctly allocated block of memory as the string itself. Either this convention is followed and everything's fine or undefined behaviour occurs.
If you know the length of the string you compare against you can use strncmp() but his will not help if the string passed to your code is actually shorter than the string you compare against.
you can use strncmp, But if possible use std::string to avoid many problems :)
You can put an upper limit on the number of characters to be compared using the strncmp function.
There is no best answer to this as you can't verify the char* is a string. The only solution is to create a type and use it for string for example str::string or create your own if you want something lighter. ie
struct MyString
{
MyString() : str(0), len(0) {}
MyString( char* x ) { len = strlen(x); str = strdup(x); }
⁓MyString() { if(str) free(str); }
char* str;
size_t len;
};
bool IsSameString(MyString& p1, MyString& p2)
{
return 0 == strcmp(p1.str, p2.str);
}
MyString str1("test");
MyString str2("test");
if( IsSameString( str1, str2 ) {}
You dont write, what platform you are using. Windows has the following functions:
IsBadStringPtr
IsBadReadPtr
IsBadWritePtr
IsBadStringPtr might be what you are looking for, if you are using windows.