I got an issue with sprintf buffer.
As you can see in the code down below I'm saving with sprintf a char array to the buffer, so pFile can check if there's a file named like that in the folder. If it's found, the buffer value will be assigned to timecycles[numCycles], and numCycles will be increased. Example: timecycles[0] = "timecyc1.dat". It works well, and as you can see in the console output it recognizes that there are only timecyc1.dat and timecyc5.dat in the folder. But as long as I want to read timecycles with a for loop, both indexes have the value "timecyc9.dat", eventhough it should be "timecyc1.dat" for timecycles[0] and "timecyc5.dat" for timecycles1. Second thing is, how can I write the code so readTimecycles() returns char* timecycles, and I could just initialize it in the main function with char* timecycles[9] = readTimecycles() or anything like that?
Console output
#include <iostream>
#include <cstdio>
char* timecycles[9];
void readTimecycles()
{
char buffer[256];
int numCycles = 0;
FILE* pFile = NULL;
for (int i = 1; i < 10; i++)
{
sprintf(buffer, "timecyc%d.dat", i);
pFile = fopen(buffer, "r");
if (pFile != NULL)
{
timecycles[numCycles] = buffer;
numCycles++;
std::cout << buffer << std::endl; //to see if the buffer is correct
}
}
for (int i = 0; i < numCycles; i++)
{
std::cout << timecycles[i] << std::endl; //here's the issue with timecyc9.dat
}
}
int main()
{
readTimecycles();
return 0;
}
With the assignment
timecycles[numCycles] = buffer;
you make all pointers point to the same buffer, since you only have a single buffer.
Since you're programming in C++ you could easily solve your problem by using std::string instead.
If I would remake your code into something a little-more C++-ish and less C-ish, it could look something like
std::array<std::string, 9> readTimeCycles()
{
std::array<std::string, 9> timecycles;
for (size_t i = 0; i < timecycles.size(); ++i)
{
// Format the file-name
std::string filename = "timecyc" + std::to_string(i + 1) + ".dat";
std::ifstream file(filename);
if (file)
{
// File was opened okay
timecycles[i] = filename;
}
}
return timecycles;
}
References:
std::array
std::string
std::to_string
std::ifstream
The fundamental problem is that your notion of a string doesn't match what a 'char array' is in C++. In particular you think that because you assign timecycles[numCycles] = buffer; somehow the chars of the char array are copied. But in C++ all that is being copied is a pointer, so timecycles ends up with multiple pointers to the same buffer. And that's not to mention the problem you will have that when you exit the readTimecycles function. At that point you will have multiple pointers to a buffer which no longer exists as it gets destroyed when you exit the readTimecycles function.
The way to fix this is to use C++ code that does match your expectations. In particular a std::string will copy in the way you expect it to. Here's how you can change your code to use std::string
#include <string>
std::string timecycles[9];
timecycles[numCycles] = buffer; // now this really does copy a string
Related
I have array of struct in class,and I want save that in file.
if I put the input ac.mem [i] .username except the username is stored in the file
And if I put the input ac.mem [i] nothing will be saved.
This is part of my code:
const int len=5;
class account {
public:
struct members {
string username;
string password;
int acsess;
}mem[len];
};
class account ac;
....
ac.mem[0] = { "admin","soran",5 };
ac.mem[1] = { "hamid","hamid",4 };
fstream acc1("account", ios::binary);
for (int i = 0; i <= 1; i++) {
acc1.write((char*)&ac.mem[i].username, sizeof(ac.mem[i].username));
}
acc1.close();
....
ifstream acc2("account", ios::binary);
for (int i = 0; i <= len; ++i) {
acc1.read((char*)&ac.mem[i].username, sizeof(ac.mem[i].username));
cout << i + 1 << "." << setw(10) << ac.mem[i].username << setw(20) << ac.mem[i].password << setw(20) << ac.mem[i].acsess << endl;
}
acc2.close();
std::string objects are pretty complex types – they internally maintain pointers to memory. When you just write the internal representation to a file (casting address of to char*) all you write out are these pointers plus possibly some additional management data.
The actual string contents, though, are stored at the locations these pointers point to. When reading back you cannot ever assume to find the same data at the address you've just restored from file (unless the original string object written to still exists – but then playing around with the internals will, if two different std::string objects involved, with 100% probability lead to undefined behaviour due to double deletion, if not reading and writing them from/to memory that way already is).
What you actually want to print to file are the contents of the string – which you get by either std::string::c_str or alternatively std::string::data. You might additionally want to include the terminating null character (hopefully there are no internal ones within the string...) to be able to read back multiple strings, stopping reading each one at exactly the null terminator, then writing to file might look like:
std::string s; // assign some content...
std::ofstream f; // open some path
if(f) // stream opened successfully?
{
f.write(s.c_str(), s.length() + 1);
}
Note that std::string::length returns the length without the terminating null character, so if you want/need to include it, you need to add one to as done above.
Alternatively you can write out the string's length first and then skip writing the null character – with the advantage that on reading back you already know in advance how many characters to read and thus to pre-allocate within your objects (std::string::reserve). For compatibilty reasons over different compilers and especially machines make sure to write out fixed-size data types from <cstdint> header, e.g.:
uint32_t length = s.length();
f.write(reinterpret_cast<char const*>(&length), sizeof(length));
f.write(s.c_str(), s.length());
This approach covers internally existing null characters as well (though if such data exists, std::vector<unsigned char> or preferably std::vector<uint8_t> might be better alternative, std::string is intended for texts).
If you want to use C language, you could refer to the following code.
#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable : 4996)
typedef struct {
char* name;
int phone;
}address;
int main(void)
{
int i;
address a[3];
for (i = 0; i < 3; i++)
{
a[i].name = "jojo";
a[i].phone = "123456";
}
FILE* fp;
fp = fopen("list.txt", "ab");
for (i = 0; i < 3; i++)
{
printf(" % s, % d",a[i].name,a[i].phone);
fwrite(&a[i], sizeof(address), 1, fp);
}
fclose(fp);
return 0;
}
Here is a problem. When I try to convert it by using strncpy_s, array has some type of "trash data" from memory in the end of it. Even when I fill buffer with "\0". How to convert it clear?
typedef class Ryadok {
private:
int LengthOf = 0;
char text[20];
string* address;
public:
Ryadok(string strin) {
this->text[0] = '\0';
memset(text, '\0', sizeof(text));
strncpy_s(text, strin.c_str(), sizeof(text) - 1);
this->address = &strin;
for (int i = 0; i < sizeof(strin); i++) {
cout << this->text[i];
}
}
~Ryadok() {
}
}*cPtr;
int main()
{
Ryadok example("sdsdfsdf");
}
The idea to use c_str() function to convert the std::string to a a-string. Then we can simply call strcpy() function to copu the c-string into char array
std::string s = "Hello World!";
char cstr[s.size() + 1];
strcpy(cstr, s.c_str()); // or pass &s[0]
std::cout << cstr << '\n';
return 0;
When using the strncpy_s function you tell it to copy as many chars as will fit into your buffer "text". Since the string you create the "example" instance with is shorter, the copy function will keep going after the end of the actual string.
That is where your garbage comes from. Even worse you risk a Segmentation Fault this way. Your code might access parts of the RAM it is not allowed to read from. That will cause it to crash.
You are right though to copy the data pointed to by the return of c_str(). The pointer returned by c_str() points to data that belongs to the std::string object and might be changed or even invalidated by that object. (Read more here)
Here's a modified version of your code that should avoid the garbage:
typedef class Ryadok {
private:
int LengthOf = 0;
char text[20];
string* address;
public:
Ryadok(string strin) {
this->text[0] = '\0';
memset(text, '\0', sizeof(text));
if(strin.length()+1 <= sizeof(text)) {
strncpy_s(text, strin.c_str(), strin.length()+1);
} else {
//some error handling needed since our buffer is too small
}
this->address = &strin;
for (int i = 0; i < sizeof(strin); i++) {
cout << this->text[i];
}
}
~Ryadok() {
}
}*cPtr;
int main()
{
Ryadok example("sdsdfsdf");
}
I'm trying to read a txt file, and put it into an char array. But can I read different files which contain different length of characters and put them into an array. Can I create a dynamic array to contain unknown length of characters.
You can read a file of unknown size into a dynamics data structure like:
std::vector More info here.
Alternatively, you can use new to allocate a dynamic memory. However, vectors are more convenient at least to me :).
#include <vector>
#include <iostream>
#include <fstream>
int main(int argc, char **argv)
{
std::vector<std::string> content;
if (argc != 2)
{
std::cout << "bad argument" << std::endl;
return 0;
}
std::string file_name (argv[1]);
std::ifstream file(file_name);
if (!file)
{
std::cout << "can't open file" << std::endl;
return 0;
}
std::string line = "";
while (std::getline(file, line))
{
content.push_back(line);
line = "";
}
for (std::vector<std::string>::iterator it = content.begin(); it != content.end(); ++it)
std::cout << *it << std::endl;
}
here is a solution using std::vectors and std::string
the programm takes a file name as first parameter, opens it, read it line by line
each line is written in the vector
then you can display your vector as i did at the end of the function
EDIT: because C++11 is the new standars, the program use C++11 then you have to compile it using c++11 (g++ -std=c++11 if you use g++)
I just tested it it works perfectly
There may be library routines available which give you the size of the file without reading the contents of the file. In that case you could get the size and allocate a full-sized buffer, and suck in the whole file at once [if your buffer is a simple char array, don't forget to add one and put in the trailing nullchar].
The best way is use of malloc(), realloc(), and free() just like it was an old C program. If you try to use a std::vector you will choke approaching maximum RAM as realloc() can grow and shrink in place (grow is contingent on heap while shrink is guaranteed to work) while std::vector cannot do so.
In particular:
#include <iostream>
#include <tuple>
// TODO perhaps you want an auto-management class for this tuple.
// I'm not about to type up the whole auto_ptr, shared_ptr, etc.
// Mostly you don't do this enough to have to worry too hard.
std::tuple<char *, size_t> getarray(std::istream &input)
{
size_t fsize = 0;
size_t asize = 0;
size_t offset = 0;
size_t terminator = (size_t)-1;
char *buf = malloc(asize = 8192);
if (!buf) throw std::bad_alloc();
char *btmp;
char shift = 1;
do {
try {
input.read(buf + offset, asize - fsize);
} catch (...) {
free(buf);
throw;
}
if (input.gcount == 0) {
btmp = realloc(buf, bufsize);
if (btmp) buf = btmp;
return std::tuple<char *, size_t>(buf, fsize);
}
offset += input.gcount;
fsize += offset;
if (fsize == asize) {
if (shift) {
if ((asize << 1) == 0)
shift = 0;
else {
btmp = realloc(buf, asize << 1);
if (!btmp)
shift = 0;
else {
asize <<= 1;
buf = btmp;
}
}
if (!shift) {
btmp = realloc(buf, asize += 8192);
if (!btmp) {
free(buf);
throw std::bad_alloc();
}
}
}
}
} while (terminator - offset > fsize);
free(buf);
// Or perhaps something suitable.
throw "File too big to fit in size_t";
}
I'm trying to use ifstream/ofstream to read/write but for some reason, the data gets corrupted along the way. Heres the read/write methods and the test:
void FileWrite(const char* FilePath, std::vector<char> &data) {
std::ofstream os (FilePath);
int len = data.size();
os.write(reinterpret_cast<char*>(&len), 4);
os.write(&(data[0]), len);
os.close();
}
std::vector<char> FileRead(const char* FilePath) {
std::ifstream is(FilePath);
int len;
is.read(reinterpret_cast<char*>(&len), 4);
std::vector<char> ret(len);
is.read(&(ret[0]), len);
is.close();
return ret;
}
void test() {
std::vector<char> sample(1024 * 1024);
for (int i = 0; i < 1024 * 1024; i++) {
sample[i] = rand() % 256;
}
FileWrite("C:\\test\\sample", sample);
auto sample2 = FileRead("C:\\test\\sample");
int err = 0;
for (int i = 0; i < sample.size(); i++) {
if (sample[i] != sample2[i])
err++;
}
std::cout << err << "\n";
int a;
std::cin >> a;
}
It writes the length correctly, reads it correctly and starts reading the data correctly but at some point(depending on input, usually at around the 1000'th byte) it goes wrong and everything to follow is wrong. Why is that?
for starter, you should open the file stream for binary read and write :
std::ofstream os (FilePath,std::ios::binary);
(edit: assuming char really means "signed char")
Do notice that regular char can hold up to CHAR_MAX/2 value, which is 127.
If the random number is bigger - the result will wrap around, resulting negative value. the stream will try to write this character as a text character, which is invalid value to write. binary format should at least fix this problem.
Also, you shouldn't close the stream yourself here, the destructor does it for you.
Two more simple points:
1) &(data[0]) should be just &data[0], the () are redundant
2) try keep the same convention. you write upper-camel-case for FilePath variable, but lower-camel-case for all the other variables.
This problem is blowing my mind right now.
int main()
{
char inputChar;
char *buffer = nullptr;
int size = 0;
read(buffer); //this is the line causing problems...
int numberOfFrames = (size / MAX_FRAME_SIZE) + 1;
frame array[numberOfFrames];
for(int i = 0; i < size; i++)
{
buffer[i] = appendParityBit(buffer[i]);
}
constructFrame(buffer, size, array);
transmitFrames(array, numberOfFrames);
}
int read(char *buffer)
{
int fileSize;
ifstream myfile ("inputFile");
if (myfile.is_open())
{
fileSize = getFileLength(myfile);
buffer = new char[fileSize];
myfile.read(buffer, fileSize);
myfile.close();
}
return fileSize;
}
int getFileLength(ifstream &myfile)
{
myfile.seekg(0, ios::end);
int size = (int) myfile.tellg() - 1;
myfile.seekg(0, ios::beg);
return size;
}
now if i do a
cout << read(buffer);
on the line that is causing problems, i receive an integer back...great, perfect. but if i try to do
size = read(buffer);
my program crashes...i'm at a loss.
You are passing a variable by value (doesn't matter if it is a pointer or not). On the receiving end, the function makes a local copy of what is passed, works with the local copy, and poof, the local copy goes away when the function returns.
This occurs regardless of whether what you're passing is a pointer or not. For example, take this simple code:
void foo(int x)
{
x = 10;
}
int main()
{
int val = 0;
foo(val);
cout << val; // how come val is still 0 and not 10?
}
Note that val is still 0, even though the function is changing the parameter that is being passed. To fix this problem, you pass a reference to the value that will be changed:
void foo(int& x)
{
x = 10;
}
int main()
{
int val = 0;
foo(val);
cout << val; // now val is 10
}
With pointers, the rules don't change. You need to pass a reference to the pointer to have the change reflect back to the caller:
int read(char*& buffer)
{
int fileSize;
ifstream myfile ("inputFile");
if (myfile.is_open())
{
fileSize = getFileLength(myfile);
buffer = new char[fileSize];
myfile.read(buffer, fileSize);
myfile.close();
}
return fileSize;
}
Now the buffer in that function is not a local copy, but a reference to the variable you passed.
The other method (which is more "C" style) is to pass a pointer to the thing you want to change. You want to change the pointer value, so you pass a pointer to the pointer:
int read(char** buffer)
{
int fileSize;
ifstream myfile ("inputFile");
if (myfile.is_open())
{
fileSize = getFileLength(myfile);
*buffer = new char[fileSize];
myfile.read(buffer, fileSize);
myfile.close();
}
return fileSize;
}
// the caller
char *buffer;
//...
read(&buffer);
Of course, we have to change the syntax since it is a pointer that is being passed, thus we need to dereference it.
You are passing your buffer (char*) by value. Even when you allocate the buffer in your read() routine, this modifies local copy of the pointer. When you return from read(), you still have the old unititialized value of the pointer, which is not usable. To alleviate the issue, you can pass your buffer by reference.
I think the other answers have identified your coding error.
I note that you have tagged this as C++ ... and I suggest that perhaps your error would not have occurred if you used C++ features.
I have found the following (in SO and else where). It is similar, but relies on the strongly tested std::string memory management, and the file size requires no extra code on your part.
size_t read(std::string& buffer)
{
std::ifstream sIn("inputFile");
if (!sIn.is_open())
{
std::stringstream ssErr;
ssErr << "Can not open file '" << "inputFile" << "'" << std::endl;
throw ssErr.str();
}
std::stringstream ss(buffer);
ss << sIn.rdbuf(); // one line transfer of file contents
sIn.close(); // close sIn when we are done with it
if(sIn.bad())
throw "Err: sIn.rdbuf()";
return (ss.str().size());
}
Somewhere you might want a std::char array ... note that the c-style result (null terminated string) is available in buffer.c_str().
You can load any size text file (that fits in your memory) using this technique.