Jump to content
  • Advertisement
Sign in to follow this  
irbaboon

Putting data into a char* or std::string

This topic is 2479 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey

I want to put ints, doubles etc. into a char array, for a buffer.
But right now I just get random data.



// function to use
char *Function(void *Data, size_t Size)
{
char DataChar[50000];
memcpy(DataChar, Data, Size);
return DataChar;
}

// using
int Integer = 532;
float Float = 63.256;
std::string String = "This is a string.";
std::string Buffer = Function(&Integer, sizeof(int));
Buffer += Function(&Float, sizeof(float));
Buffer += Function(&String, String.size());


I've tried to solve this myself, and the code above is what looks most right to me. But still doesn't work...
Please help me :)

Thanks in advance ^_^

Share this post


Link to post
Share on other sites
Advertisement
your returning a stack allocated temporary array. That's undefined behavior, and your compiler is probably emitting a warning about that.

Share this post


Link to post
Share on other sites

your returning a stack allocated temporary array. That's undefined behavior, and your compiler is probably emitting a warning about that.

You mean that I'm returning DataChar?
That's not really what's happening, I just wanted to explain it in a simple way... didn't think about it.

Edit:
Tested out the function I wrote now, it does give a warning.
Thanks for pointing it out, but this doesn't answer my question :(

Share this post


Link to post
Share on other sites
There are several problems here: First, you cannot return a pointer to temporary memory -- the memory allocated for DataChar is invalidated as soon as Function returns, making it completely useless; second, while you can concatenate std::strings together, its more idiomatic in C++ to use stringsreams, and thirdly, with all of this combined together, you can use templates to make everything typesafe:


template<class Type>
istream& writeBinary( ostream& s, const Type& val ) {
s.write( (const char*)&val, sizeof(Type) );
return s;
}

template<>
istream& writeBinary( ostream& s, const string& str ) {
s.write( str.c_str(), str.length() );
s.put( '\0' );
return s;
}


stringstream ss;
writeBinary( ss, 5 );
writeBinary( ss, string( "This is a string" ) );
writeBinary( ss, 1.0f );
// etc..


EDIT:
I regrettably did not spend enough time fleshing out this post; Wahu's got my back though.

Share this post


Link to post
Share on other sites
Well, kudos to you for trying. But it'll never work like that. floats and ints aren't stored as strings in memory, so you can't just convert them like that. Here are a couple popular options:

Using boost::lexical_cast (requires Boost)


int Integer = 532;
float Float = 3.141592f;
std::string buffer = "This is a string.";
buffer += boost::lexical_cast<std::string>(Integer);
buffer += boost::lexical_cast<std::string>(Float);


Using std::stringstream

float Float = 3.141592f;
std::stringstream stream;
stream << Float;
std::string buffer;
stream >> buffer;


[edit]

Oh goody, there were 3 posts in the time it took me to write 1! ninja'd += 3ish.

[edit edit]

Wait, are you wanting to convert it to strings or output it in its binary form?

Share this post


Link to post
Share on other sites

There are several problems here: First, you cannot return a pointer to temporary memory -- the memory allocated for DataChar is invalidated as soon as Function returns, making it completely useless; second, while you can concatenate std::strings together, its more idiomatic in C++ to use stringsreams, and thirdly, with all of this combined together, you can use templates to make everything typesafe:


template<class Type>
istream&amp; writeBinary( istream&amp; s, const Type&amp; val ) {
s.write( (const char*)&amp;val, sizeof(Type) );
return s;
}

template<>
istream&amp; writeBinary( istream&amp; s, const string&amp; str ) {
s.write( str.c_str(), str.length() );
s.put( '\0' );
return s;
}


stringstream ss;
writeBinary( ss, 5 );
writeBinary( ss, string( "This is a string" ) );
writeBinary( ss, 1.0f );
// etc..


and it breaks silently the first time you pass in a char* by accident. also, use reinterpret_cast, just to be more C++y

Share this post


Link to post
Share on other sites

There are several problems here: First, you cannot return a pointer to temporary memory -- the memory allocated for DataChar is invalidated as soon as Function returns, making it completely useless; second, while you can concatenate std::strings together, its more idiomatic in C++ to use stringsreams, and thirdly, with all of this combined together, you can use templates to make everything typesafe:


template<class Type>
istream& writeBinary( istream& s, const Type& val ) {
s.write( (const char*)&val, sizeof(Type) );
return s;
}

template<>
istream& writeBinary( istream& s, const string& str ) {
s.write( str.c_str(), str.length() );
s.put( '\0' );
return s;
}


stringstream ss;
writeBinary( ss, 5 );
writeBinary( ss, string( "This is a string" ) );
writeBinary( ss, 1.0f );
// etc..



and it breaks silently the first time you pass in a char* by accident. also, use reinterpret_cast, just to be more C++y


I see. Thanks, I'll experiement with that.
And Washu, what do you mean?
I have never used reinterpret_cast before... I can look it up I guess ^^

Share this post


Link to post
Share on other sites
Ok, I will simply assume that you do NOT want to convert your data into an actual string and have int x = 20 turn into char* = "20".

So first, your function is rather pointless and doesn't do anything that brute force casting your data to char* wouldn't do (without blowing up the stack with a ridiculously large array and pointless copying).

Second, even if this WOULD be working, you at no point seem to terminate that pseudo string you create with a 0. Trying to concatenate it with a string will just keep reading and copying until it hits the first zero that happens to be in memory. Best case: access violation to let you know there is a problem, worst case: seems to work most of the time and in debug builds, but suddenly sometimes breaks or copies garbage.

Third: please don't abuse the string class like that. If you want nothing but a generic buffer for data, use vector<uint8_t>. Anybody seeing "string" expects it to contain actual text, not raw binary data (also, most of the functions of string don't make any sense or will just break in a thousand ways).

The ugly way to do it quick and dirty would be something like:

buffer.insert( buffer.end(), (char*)&MyData, ((char*)&MyData) + sizeof(MyData) );

Now this contains lots of ugly code, should also use a reinterpret_cast<char*>(&MyData), probably be split over 2 lines and it will still break if you use it on anything but native types (ie. do NOT try to use it on c-strings or arrays or stl containers). Yes, it could be used for structs, but if you try to use it to pass data to another program it can break (since you can't rely on it having the same layout in memory). Pragma pack should be looked at. Also byte order if using different platforms. It will also fail hard if your struct contains pointers or anything that requires more than a shallow copy.

While this might look a little cleaner, I would still consider it a case of "please only do this if you really know what you're doing":
const char* rawPtr = reinterpret_cast<const char*>(&MyData);
buffer.insert( buffer.end(), rawPtr, rawPtr + sizeof(MyData) );


You can use templates to hide it and save some work, but should be very careful to have specializations for some types (strings, pointers, arrays) and think REALLY hard about what happens if someone uses this on classes with virtual functions, containing stl containers, etc.

The real question might be: what exactly are you trying to do and what do you need that buffer for?

Share this post


Link to post
Share on other sites
Here is an example that solves some of the problems with fastcall's code. Mainly it deals with the issue of char*, and also with pointer type problems (by using SFINAE). It also fixes the erroneous use of istream instead of ostream.

#include <iostream>
#include <string>
#include <type_traits>
#include <sstream>
#include <cstdlib>
template<class T>
typename std::enable_if<std::is_fundamental<T>::value, std::ostream>::type&
write_binary(std::ostream& s, T const& value) {
s.write(reinterpret_cast<char const*>(&value), sizeof(T));
return s;
}
template<class CharType, class CharTraits>
std::ostream&
write_binary(std::ostream& s, std::basic_string<CharType, CharTraits> const& str) {
s.write(reinterpret_cast<char const*>(&str.front()), str.length() * sizeof(CharType));
return s;
}
std::ostream& write_binary(std::ostream& s, std::string const& str) {
s.write(reinterpret_cast<char const*>(&str.front()), str.length());
return s;
}
std::ostream& write_binary(std::ostream& s, std::wstring const& str) {
s.write(reinterpret_cast<char const*>(&str.front()), str.length() * sizeof(std::wstring::value_type));
return s;
}
std::ostream& write_binary(std::ostream& s, char const* data, size_t length) {
s.write(data, length);
return s;
}
struct S {
int a;
char* b;
};
int main() {
std::ostringstream ss;
write_binary(ss, 5);
int mynumber = rand();
write_binary(ss, mynumber);
write_binary(ss, "Hello world");
write_binary(ss, L"Hello world");
write_binary(ss, "Hello world", 11);
// write_binary(ss, &mynumber); // fails, pointer to T is excluded by enable_if, thus no appropriate overload is found.
// write_binary(ss, S); // fails, S is not a fundamental type. if an overload was available it would succeed though.
for(auto val : ss.str()) {
std::cout<<std::hex<<static_cast<int>(val)<<" ";
}
std::endl(std::cout);
}

Share this post


Link to post
Share on other sites


int WriteBinary(FILE* fp,uint64_t value)
{
return fwrite(&value,1,8,fp);
}

int WriteBinary(FILE* fp,const char* value)
{
return fwrite(value,1,strlen(value),fp);
}

int WriteBinary(FILE* fp,char* value)
{
return WriteBinary(fp,(const char*)value);
}

int WriteBinary(FILE* fp,void* d,uint32_t l)
{
return fwrite(d,1,l,fp);
}

int WriteBinary(FILE* fp,const string& value)
{
return WriteBinary(fp,(const char*)value.c_str());
}

int main()
{
FILE* fp = fopen("file","wb");
double d = 1234.56789;
float f = 1234.1234f;
int i = 1234;
long long int l = 99999999;
const char* s = "gggggg";
string ss = "ccccc";
const char* sss = "????";

WriteBinary(fp,d);
WriteBinary(fp,f);
WriteBinary(fp,i);
WriteBinary(fp,l);
WriteBinary(fp,s);
WriteBinary(fp,ss);
WriteBinary(fp,sss,strlen(sss));
fclose(fp);
return 0;
}


What do i win?

Edit1 : if you would like to store strings first write their length followed by the literal.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!