missing data in popen call - c++

my program compiles without error and appears to run through all of the steps correctly. It is supposed to make a php call and return data. tcpdump does show the request going out so popen is being executed, but the receiving party never updates.
The only discrepancy I can find, is that the command variable appears to be missing data.
# .trol.o
market max price is 0.00638671 at position 0
php coin.php 155 0.006387
0.00638672
the second line in the output is the command I am sending to popen
cout << command << endl; -> php coin.php 155 0.006387
that number is supposed to be the same as the one under it 0.00638672
The number 6 and the number 2 have been chopped off somehow.
How do I get the correct data into my popen command?
code:
void mngr(){
//vector defs
vector<std::string> buydat;
vector<std::string> markdat;
vector<std::string> pricedat;
vector<std::string> purchaseid;
vector<double> doublePdat;
vector<double> doubleMdat;
doublePdat.reserve(pricedat.size());
doubleMdat.reserve(markdat.size());
char buybuff[BUFSIZ];
char command[70];
char sendbuy[12];
buydat = getmyData();
markdat = getmarketbuyData();
//string match "Buy" and send results to new vector with pricedat.push_back()
for(int b = 2; b < buydat.size(); b+=7){
if ( buydat[b] == "Buy" ) {
pricedat.push_back(buydat[b+1]);
}
}
transform(pricedat.begin(), pricedat.end(), back_inserter(doublePdat), [](string const& val) {return stod(val);});
transform(markdat.begin(), markdat.end(), back_inserter(doubleMdat), [](string const& val) {return stod(val);});
auto biggestMy = std::max_element(std::begin(doublePdat), std::end(doublePdat));
std::cout << "my max price is " << *biggestMy << " at position " << std::distance(std::begin(doublePdat), biggestMy) << std::endl;
auto biggestMark = std::max_element(std::begin(doubleMdat), std::end(doubleMdat));
std::cout << "market max price is " << *biggestMark << " at position " << std::distance(std::begin(doubleMdat), biggestMark) << std::endl;
if (biggestMy > biggestMark){
cout << "Biggest is Mine!" << endl;
}
else if (biggestMy < biggestMark){
//cout << "Biggest is market!";
*biggestMark += 0.00000001;
sprintf(sendbuy,"%f",*biggestMark);
sprintf(command, "php coin.php 155 %s",sendbuy);
FILE *markbuy = popen(command, "r");
if (markbuy == NULL) perror ("Error opening file");
while(fgets(buybuff, sizeof(buybuff), markbuy) != NULL){
size_t h = strlen(buybuff);
//clean '\0' from fgets
if (h && buybuff[h - 1] == '\n') buybuff[h - 1] = '\0';
if (buybuff[0] != '\0') purchaseid.push_back(buybuff);
}
cout << command << endl;
cout << *biggestMark << endl;
}
}

I would try to use long float format instead of float as the type of biggestMark should be evaluated as iterator across doubles. I mean try to change sprintf(sendbuy,"%f",*biggestMark); to sprintf(sendbuy,"%lf",*biggestMark);. Hope this would help.

Related

C++ Searching an array for a number but does not trigger else statement if it is not found

int gas;
// Input Code
int user_code;
std::cout << std::endl;
std::cout << "Please enter the Code: ";
std::cin >> user_code;
std::cout << "The value you entered is " << user_code;
std::cout << std::endl;
int array1[16] = { 42011, 42017, 42029, 42045,
42091, 42101, 34001, 34005,
34007, 34009, 34011, 34015,
34033, 10001, 10003, 24015 }; // 0.2387 (23.87%)
int array2[45] = { 11001, 24003, 24510, 24005, 24009,
24013, 24017, 24019, 24021, 24025,
24027, 24029, 24031, 24033, 24035,
24037, 24041, 24043, 51510, 51013,
51043, 51047, 51600, 51059, 51610,
51061, 51069, 51630, 51099, 51107,
51683, 51685, 51153, 51157, 51177,
51179, 51187, 51840, 54003, 54027,
54037, 54065, 42001, 42055, 42133 }; //0.2710 (27.10%)
int * array1_search;
array1_search = std::find(array1, array1+ 16, user_code);
int * array2_search;
array2_search = std::find(array2, array2 + 45, user_code);
if (array1_search != array1+ 16) {
std::cout << "Codefound in Array1: " << *array1_search << '\n';
gas= 0.2387;
}
else if (array2_search != array2_search + 45) {
std::cout << "Code found in Array2: " << *array2_search << '\n';
gas= 0.2710;
}
else {
std::cout << "Not found \n";
gas= 0.1506;
}
Above is my current code. I am trying to have the user input a variable user_code value and then iterate over the two arrays array1[16] and array2[45]. If the user input value is on the first array1 I want to assign gas 0.2387 and if the input value is on the other array2 I want to assign gas 0.2710, and if it is not within any array gas should be 0.1506.
So basically I want to assign a value depending on which array the user's input is contained in. I am very new to c++, what is the best way to go about this?
It seems to work fine if I enter a number that is within array1 or array2 and it correctly identifies that is found in array1 or array2. The problem is when I enter a number I know is not within either array to trigger the else statement it identifies it as being in array2. For example, when I enter 12345 as a user_code it says "Code found in Array2: 0". I know 12345 is not contained in array2 and I do not understand why *array2_search is assigned 0. What can I do to fix this so if a user_code is entered that is not contained within array1 or array2 it goes to the else statement?
else if (array2_search != array2_search + 45) {
Should be
else if (array2_search != array2 + 45) {
or better using std::end of C++11:
if (array1_search != std::end(array1)) {
else if (array2_search != std::end(array2)) {
And int gas; => double gas; if you want to be able to store floating point values, not just integers (0.2387 and 0.2710 would give integer 0).
Using standard containers and newer c++ features if you have C++11 minimum you can then do something like this:
int main() {
// Use Constants Instead of "Hard Coded Values"
// If you noticed these are not even needed.
// const unsigned code1 = 16;
// const unsigned code2 = 45;
// Made gas a float instead of an int due to the decimal values
// I also initialized it with the default value if the code is
// not found in either container.
float gas = 0.1506f; // Default Price If Not Found
// created your first array as a const std::vector<int> and
// used its initializer list to populate its contents: this vector
// can not be modified: remove the const if this container
// will need to have entries added in the future.
const std::vector<int> arr1 { 42011, 42017, 42029, 42045,
42091, 42101, 34001, 34005,
34007, 34009, 34011, 34015,
34033, 10001, 10003, 24015 }; // 0.2387 (23.87%)
// did the same for the second array
const std::vector<int> arr2 { 11001, 24003, 24510, 24005, 24009,
24013, 24017, 24019, 24021, 24025,
24027, 24029, 24031, 24033, 24035,
24037, 24041, 24043, 51510, 51013,
51043, 51047, 51600, 51059, 51610,
51061, 51069, 51630, 51099, 51107,
51683, 51685, 51153, 51157, 51177,
51179, 51187, 51840, 54003, 54027,
54037, 54065, 42001, 42055, 42133 }; //0.2710 (27.10%)
// No changes made here same basic user I/O.
int user_code = 0;
std::cout << "Please enter the Code: ";
std::cin >> user_code;
std::cout << "The value you entered is " << user_code;
std::cout << "\n";
// Created 2 flags for later.
bool b1found = false;
bool b2found = false;
// auto for loop ranged based.
for ( auto code : arr1 ) {
if ( code == user_code ) {
b1found = true; // Set flag
gas = 0.2387f; // Set new gas
// Output code & gas
std::cout << "Code found in Arr1: " << code << '\n';
std::cout << "gas = " << gas << '\n';
}
}
for ( auto code : arr2 ) {
if ( code == user_code ) {
b2found = true; // set flag
gas = 0.2710f; // set gas
// output code & gas
std::cout << "Code found in Arr2: " << code << '\n';
std::cout << "gas = " << gas << '\n';
}
}
// If code not found in either output "not found" and display default gas
if ( !b1found && !b2found ) {
std::cout << "Not found\n";
std::cout << "gas = " << gas << '\n';
}
std::cout << "\nPress any key and enter to quit." << std::endl;
char c;
std::cin >> c;
return 0;
}
You can even simplify this a little more by removing the two bool flags. We know that if a value is found in arr1 or arr2 that the gas value will be changed, So all we really have to do is check to see if it has been changed.
// auto for loop ranged based.
for ( auto code : arr1 ) {
if ( code == user_code ) {
gas = 0.2387f; // Set new gas
// Output code & gas
std::cout << "Code found in Arr1: " << code << '\n';
std::cout << "gas = " << gas << '\n';
}
}
for ( auto code : arr2 ) {
if ( code == user_code ) {
gas = 0.2710f; // set gas
// output code & gas
std::cout << "Code found in Arr2: " << code << '\n';
std::cout << "gas = " << gas << '\n';
}
}
const float defaultGas = 0.1506;
// If code not found in either output "not found" and display default gas
if ( gas == defaultGas ) {
std::cout << "Not found\n";
std::cout << "gas = " << gas << '\n';
}

How to implement VERIFY command on NIST PIV cards?

I must be doing something wrong, but I can't see what.
I'm trying to get the VERIFY command to show the number of attempts remaining. (I was trying to enter the PIN as well, but cut back to this when I couldn't get anything to work.) Here's the code fragment that I've been trying:
for (unsigned int basebyte = 0x00; basebyte != 0x100; basebyte += 0x80) {
for (unsigned char add = 0x01; add != 0x20; ++add) {
smartcard::bytevector_t b;
b.push_back(0x00); // CLA
b.push_back(0x20); // INS
b.push_back(0x00); // P1
b.push_back(basebyte + add); // P2 ("the sensible ranges are 0x01..0x1F and 0x81..0x9F")
//b.push_back(0x00); // Lc field -- length of the following data field
b = card.rawTransmit(b);
if (!card.status()) {
cout << "Received error '" << card.status() << "'" << endl;
} else {
if (b[0] == 0x6a && b[1] == 0x88) {
// "Referenced data not found"
continue;
}
cout << " Attempts remaining (" << std::hex << (basebyte + add) << std::dec << "): ";
cout << std::hex;
for (smartcard::bytevector_t::const_iterator i = b.begin(), ie = b.end();
i != ie; ++i) cout << std::setfill('0') << std::setw(2) << int(*i) << ' ';
cout << std::dec << endl;
}
}
}
The rawTransmit function...
bytevector_t rawTransmit(bytevector_t sendbuffer) {
SCARD_IO_REQUEST pioSendPci, pioRecvPci;
if (mProtocol.value() == SCARD_PROTOCOL_T0) {
pioSendPci = pioRecvPci = *SCARD_PCI_T0;
} else if (mProtocol.value() == SCARD_PROTOCOL_T1) {
pioSendPci = pioRecvPci = *SCARD_PCI_T1;
} else {
std::ostringstream out;
out << "unrecognized protocol '" << mProtocol.str() << "'";
throw std::runtime_error(out.str());
}
DWORD rlen = 256;
bytevector_t recvbuffer(rlen);
mResult = SCardTransmit(mHandle, &pioSendPci, &sendbuffer[0],
DWORD(sendbuffer.size()), &pioRecvPci, &recvbuffer[0], &rlen);
recvbuffer.resize(rlen);
return recvbuffer;
}
(bytevector_t is defined as std::vector<unsigned char>.)
All the cards using protocol T0 return 0x6a 0x88 ("Referenced data not found") for all P2 values. All the cards using T1 do the same, except when P2 is 0x81 -- then they say 0x69 0x84 ("Command not allowed, referenced data invalidated").
The cards in question definitely DO have PINs, and I can verify the PIN in the "Security Token Configurator" program provided by the middleware vendor, so I know that the card, reader, and middleware stuff are all working.
It's probably obvious, but I'm new to smartcard programming. Can anyone give me a clue where I'm going wrong?
The Global PIN has ID 00 and the PIV Card Application PIN has 80 (hex) so your tests do not include the known PIV card PIN ID's.

C++ equivalent of C fgets

I am looking to find a C++ fstream equivalent function of C fgets. I tried with get function of fstream but did not get what I wanted. The get function does not extract the delim character whereas the fgets function used to extract it. So, I wrote a code to insert this delim character from my code itself. But it is giving strange behaviour. Please see my sample code below;
#include <stdio.h>
#include <fstream>
#include <iostream>
int main(int argc, char **argv)
{
char str[256];
int len = 10;
std::cout << "Using C fgets function" << std::endl;
FILE * file = fopen("C:\\cpp\\write.txt", "r");
if(file == NULL){
std::cout << " Error opening file" << std::endl;
}
int count = 0;
while(!feof(file)){
char *result = fgets(str, len, file);
std::cout << result << std::endl ;
count++;
}
std::cout << "\nCount = " << count << std::endl;
fclose(file);
std::fstream fp("C:\\cpp\\write.txt", std::ios_base::in);
int iter_count = 0;
while(!fp.eof() && iter_count < 10){
fp.get(str, len,'\n');
int count = fp.gcount();
std::cout << "\nCurrent Count = " << count << std::endl;
if(count == 0){
//only new line character encountered
//adding newline character
str[1] = '\0';
str[0] = '\n';
fp.ignore(1, '\n');
//std::cout << fp.get(); //ignore new line character from stream
}
else if(count != (len -1) ){
//adding newline character
str[count + 1] = '\0';
str[count ] = '\n';
//std::cout << fp.get(); //ignore new line character from stream
fp.ignore(1, '\n');
//std::cout << "Adding new line \n";
}
std::cout << str << std::endl;
std::cout << " Stream State : Good: " << fp.good() << " Fail: " << fp.fail() << std::endl;
iter_count++;
}
std::cout << "\nCount = " << iter_count << std::endl;
fp.close();
return 0;
}
The txt file that I am using is write.txt with following content:
This is a new lines.
Now writing second
line
DONE
If you observe my program, I am using fgets function first and then using the get function on same file. In case of get function, the stream state goes bad.
Can anyone please point me out what is going wrong here?
UPDATED: I am now posting a simplest code which does not work at my end. If I dont care about the delim character for now and just read the entire file 10 characters at a time using getline:
void read_file_getline_no_insert(){
char str[256];
int len =10;
std::cout << "\nREAD_GETLINE_NO_INSERT FUNCITON\n" << std::endl;
std::fstream fp("C:\\cpp\\write.txt", std::ios_base::in);
int iter_count = 0;
while(!fp.eof() && iter_count < 10){
fp.getline(str, len,'\n');
int count = fp.gcount();
std::cout << "\nCurrent Count = " << count << std::endl;
std::cout << str << std::endl;
std::cout << " Stream State : Good: " << fp.good() << " Fail: " << fp.fail() << std::endl;
iter_count++;
}
std::cout << "\nCount = " << iter_count << std::endl;
fp.close();
}
int main(int argc, char **argv)
{
read_file_getline_no_insert();
return 0;
}
If wee see the output of above code:
READ_GETLINE_NO_INSERT FUNCITON
Current Count = 9
This is a
Stream State : Good: 0 Fail: 1
Current Count = 0
Stream State : Good: 0 Fail: 1
You would see that the state of stream goes Bad and the fail bit is set. I am unable to understand this behavior.
Rgds
Sapan
std::getline() will read a string from a stream, until it encounters a delimiter (newline by default).
Unlike fgets(), std::getline() discards the delimiter. But, also unlike fgets(), it will read the whole line (available memory permitting) since it works with a std::string rather than a char *. That makes it somewhat easier to use in practice.
All types derived from std::istream (which is the base class for all input streams) also have a member function called getline() which works a little more like fgets() - accepting a char * and a buffer size. It still discards the delimiter though.
The C++-specific options are overloaded functions (i.e. available in more than one version) so you need to read documentation to decide which one is appropriate to your needs.

const char * changing value during loop

I have a function that iterates through a const char * and uses the character to add objects to an instance of std::map if it is one of series of recognized characters.
#define CHARSEQ const char*
void compile(CHARSEQ s) throw (BFCompilationError)
{
std::cout << "#Receive call " << s << std::endl;
for(int i = 0; s[i] != '\0'; i++)
{
if (std::string("<>-+.,[]").find_first_of(s[i]) == std::string::npos)
{
throw BFCompilationError("Unknown operator",*s,i);
}
std::cout << "#Compiling: " << s[i] << std::endl;
std::cout << "#address s " << (void*)s << std::endl;
std::cout << "#var s " << s << std::endl;
controlstack.top().push_back(opmap[s[i]]);
}
}
The character sequence passed is "++++++++++."
For the first three iterations, the print statements display the expected values of '+', '+', and '+', and the value of s continues to be "+++++++++++.". However, on the fourth iteration, s becomes mangled, producing bizarre values such as 'Ð', 'öê', 'cR ', 'œk' and many other character sequences. If the line that throws the exception is removed and the loop is allowed to continue, the value of s does not change after again.
Other functions have access to s but since this is not a multithreaded program I don't see why that would matter. I am not so much confused about why s is changing but why it only changes on the fourth iteration.
I have searched SO and the only post that seems at all relevant is this one but it still doesn't answer my question. (Research has been difficult because searching "const char* changing value" or similar terms just comes up with hundreds of posts about what part of is is const).
Lastly, I know I should probably be using std::string, which I will if no answers come forth, but I would still like to understand this behavior.
EDIT:
Here is the code that calls this function.
CHARSEQ text = load(s);
std::cout << "#Receive load " << text << std::endl;
try
{
compile(text);
}
catch(BFCompilationError& err)
{
std::cerr << "\nError in bf code: caught BFCompilationError #" << err.getIndex() << " in file " << s << ":\n";
std::cerr << text << '\n';
for(int i = 0; i < err.getIndex(); i++)
{
std::cerr << " ";
}
std::cerr << "^\n";
std::cerr << err.what() << err.getProblemChar() << std::endl;
return 1;
}
Where load is:
CHARSEQ load(CHARSEQ fname)
{
std::ifstream infile (fname);
std::string data(""), line;
if (infile.is_open())
{
while(infile.good())
{
std::getline(infile,line);
std::cout << "#loading: "<< line << '\n';
data += line;
}
infile.close();
}
else
{
std::cerr << "Error: unable to open file: " << fname << std::endl;
}
return std::trim(data).c_str();
}
and the file fname is ++++++++++. spread such that there is one character per line.
EDIT 2:
Here is an example of console output:
#loading: +
#loading: +
#loading: +
#loading: +
#loading: +
#loading: +
#loading: +
#loading: +
#loading: +
#loading: +
#loading: .
#Receive load ++++++++++.
#Receive call ++++++++++.
#Compiling: +
#address s 0x7513e4
#var s ++++++++++.
#Compiling: +
#address s 0x7513e4
#var s ++++++++++.
#Compiling: +
#address s 0x7513e4
#var s ++++++++++.
#Compiling:
#address s 0x7513e4
#var s ßu
Error in bf code: caught BFCompilationError #4 in file bf_src/Hello.txt:
ßu
^
Unknown operatorß
Your load function is flawed. The const char* pointer returned by c_str() is valid only until the underlying std::string object exists. But data is a local variable in load and is cleared after return. Its buffer is not overwritten by zeroes but left as it were as free memory. Therefore printing out the value immediately after returning is likely to work but your program may put new values there and the value pointed by your pointer will change.
I suggest to use std::string as the return value of load as a workaround.

strtok read length

Here's the contents of a text file:
SQUARE 2
SQUARE
RECTANGLE 4 5
I'm trying to figure out why my strtok() loop won't take the end of the 2ND "SQUARE" and just make the length = 0. Don't fully understand the concept behind strtok either, I wouldn't mind a lecture about strtok. Here's the code:
#include <cstring>
#include <cstdlib>
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
using std::ios;
#include<iomanip>
using std::setprecision;
#include <fstream>
using std::ifstream;
const int MAX_CHARS_PER_LINE = 512;
const int MAX_TOKENS_PER_LINE = 20;
const char* const DELIMITER = " ";
int main()
{
// create a file-reading object
ifstream fin;
fin.open("geo.txt"); // open a file
if (!fin.good())
return 1; // exit if file not found
//PI
float pi = 3.14159265359;
//DIMENSIONS
float length, width, height, radius;
//AREAS, PERIMETERS, VOLUMES
float areaSquare, periSquare;
float areaRectangle, periRectangle;
float areaCube, volCube;
float areaPrism, volPrism;
float areaCircle, periCircle;
float areaCylinder, volCylinder;
// read each line of the file
while (!fin.eof())
{
// read an entire line into memory
char buf[MAX_CHARS_PER_LINE];
fin.getline(buf, MAX_CHARS_PER_LINE);
// parse the line into blank-delimited tokens
int n = 0; // a for-loop index
// array to store memory addresses of the tokens in buf
const char* token[MAX_TOKENS_PER_LINE] = {0}; // initialize to 0
// parse the line
token[0] = strtok(buf, DELIMITER); // first token
if (token[0]) // zero if line is blank
{
for (n = 1; n < MAX_TOKENS_PER_LINE; n++)
{
token[n] = strtok(0, DELIMITER); // subsequent tokens
if (!token[n] || token[n]==0) break;
}
}
if(strcmp("SQUARE", token[0]) == 0) //1
{
length = atof(token[1])?atof(token[1]):0;
areaSquare = length * length;
periSquare = 4 * length;
cout.setf(ios::fixed|ios::showpoint);
cout << setprecision(2);
cout << token[0] << ' ' << "length="<< token[1] << ' ';
cout << "Area=" << areaSquare << ' ';
cout << "Perimeter=" << periSquare << '\n';
cout.unsetf(ios::fixed|ios::showpoint);
cout << setprecision(6);
}
else if(strcmp("RECTANGLE", token[0]) == 0) //2
{
length = atof(token[1])?atof(token[1]):0;
width = atof(token[2])?atof(token[2]):0;
areaRectangle = length * width;
periRectangle = 2 * length + 2 * width;
cout.setf(ios::fixed|ios::showpoint);
cout << setprecision(2);
cout << token[0] << ' ' << "length="<< token[1] << ' ';
cout << "width=" << token[2] << ' ' ;
cout << "Area=" << areaRectangle << ' ';
cout << "Perimeter=" << periRectangle << '\n';
cout.unsetf(ios::fixed|ios::showpoint);
cout << setprecision(6);
}
else
{
cout << "End of program. Press ENTER to exit.";
cin.ignore(1000,10);
break;
}
}
}
Your segmentation fault is caused by this:
length = atof(token[1])?atof(token[1]):0;
You made the mistake of assuming that token[1] was tokenized. If you look at your 2nd 'SQUARE' you'll find that for that line it will have set token[1] to NULL. You then pass NULL to atof() which understandably errors out.
You're also using strtok() improperly. There is no reason to strcpy() from its result, because strtok() itself is a destructive operation.
So here's a lecture about strtok.
Firstly, it's evil, but so handy that you use it anyway sometimes. Tokenizers can be a pain in the butt to write.
The idea behind strtok was to create an easy tokenizer. A tokenizer is a pain in the butt to write, and the interface for it is actually fairly decent if you don't mind making it really easy to blow your computer up with it. You can use a very small amount of code to parse command line arguments, for example.
However, strtok is destructive to the string you use it on. It will replace the token that it finds with a 0, automatically null-terminating the returned value. That means that you can directly use the returned string without needing to copy it. A string like this:
here are spaces0
Is changed into
here0are0spaces0
where 0 delimits end of string character (0). This is done in place, and you get pointers to here, are, and spaces.
strtok uses static variables - meaning it retains state information between calls. On the first call you pass it a pointer to the string you're trying to tokenize; from then on, you pass it a NULL pointer to signal that you want it to continue where it left off before. It returns the next token, returning NULL when it finds the end of the string.
An strtok loop is very easy to write. This code will tokenize a string for you properly. The following example code is ugly, but I blame being tired.
char *input_string; // Just so we have it
const int MAX_TOKENS = 10; // Arbitrary number
char *tokens[MAX_TOKENS]; // This will do all the storage we need.
tokens[0] = strtok(input_string, " -=\""); // Setup call.
int number_of_tokens = 1; // We've already filled tokens[0], so we have 1 token. We want to start filling from 1.
do {
if (tokens[number_of_tokens] = strtok(NULL," -=\"")) number_of_tokens++;
else break;
} while(number_of_tokens < MAX_TOKENS);
That first line in the loop is common practice for C programmers, but is ugly for readability. Here's what it does:
a) It sets tokens[number_of_tokens] to the return value of strtok.
b) If that is NULL, it terminates the loop (second line).
addendnum: there is an inline test. You can do if (a = 1) and it will return true and set a to 1. You can do if (a = 0) it will return false while setting a to 0. This line takes advantage of that fact, if strtok() returns NULL, well, that's false.
c) If that is not NULL, tokens[number_of_tokens] now contains a pointer to the next token found in the string.
d) Since a token was found, the number of tokens (number_of_tokens) is incremented.
5) It reuses the variable that keeps count of how many tokens there are as an index into the array of pointers that it keeps.
6) It loops infinitely until it either meets the condition of strtok returning NULL, or the while() condition (in this case, there are more than 10 tokens).
If it was given this string:
here are some=words0
It would be
*tokens[0]="here"
*tokens[1]="are"
*tokens[2]="some"
*tokens[3]="words"
*tokens[4] = NULL
number_of_tokens = 4
As you can see, there's no need to copy anything, because that string is replaced in memory as such:
here0are0some0words0
where 0 delimits end of string character (0).
I hope this answers your questions.
Here is a version that works.
Main differences are,
Have changed the array of char * to array of 20 char strings. This guarantees the array elements have memory allocated, in your case they are null pointers and stay this way when strtok returns NULL, you cannot then use a NULL pointer.
The second call to strtok is "strtok(0, DELIMITER)"
but should be "strtok(NULL, DELIMITER)".
I think they are the only diffs, but use the diff utility to check.
#include <cstring>
#include <cstdlib>
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
using std::ios;
#include<iomanip>
using std::setprecision;
#include <fstream>
using std::ifstream;
const int MAX_CHARS_PER_LINE = 512;
const int MAX_TOKENS_PER_LINE = 20;
const char* const DELIMITER = " ";
int main()
{
// create a file-reading object
char *tok;
ifstream fin;
fin.open("geo.txt"); // open a file
if (!fin.good())
return 1; // exit if file not found
//PI
float pi = 3.14159265359;
//DIMENSIONS
float length, width, height, radius;
//AREAS, PERIMETERS, VOLUMES
float areaSquare, periSquare;
float areaRectangle, periRectangle;
float areaCube, volCube;
float areaPrism, volPrism;
float areaCircle, periCircle;
float areaCylinder, volCylinder;
// read each line of the file
while (!fin.eof())
{
// read an entire line into memory
char buf[MAX_CHARS_PER_LINE];
fin.getline(buf, MAX_CHARS_PER_LINE);
// parse the line into blank-delimited tokens
int n = 0; // a for-loop index
// array to store memory addresses of the tokens in buf
// const char* token[MAX_TOKENS_PER_LINE] = {0}; // initialize to 0
char token[MAX_TOKENS_PER_LINE][20];
for (n=0;n<MAX_TOKENS_PER_LINE;n++)
{
token[n][0] = NULL;
}
// parse the line
tok = strtok(buf, DELIMITER); // first token
if (tok == NULL)
break;
strcpy(token[0],tok);
if (token[0]) // zero if line is blank
{
for (n = 1; n < MAX_TOKENS_PER_LINE; n++)
{
tok = strtok(NULL, DELIMITER); // subsequent tokens
if (tok == NULL)
break;
strcpy(token[n],tok);
// if (!token[n] || token[n]==0) break;
}
}
if(strcmp("SQUARE", token[0]) == 0) //1
{
length = atof(token[1])?atof(token[1]):0;
areaSquare = length * length;
periSquare = 4 * length;
cout.setf(ios::fixed|ios::showpoint);
cout << setprecision(2);
cout << token[0] << ' ' << "length="<< token[1] << ' ';
cout << "Area=" << areaSquare << ' ';
cout << "Perimeter=" << periSquare << '\n';
cout.unsetf(ios::fixed|ios::showpoint);
cout << setprecision(6);
}
else if(strcmp("RECTANGLE", token[0]) == 0) //2
{
length = atof(token[1])?atof(token[1]):0;
width = atof(token[2])?atof(token[2]):0;
areaRectangle = length * width;
periRectangle = 2 * length + 2 * width;
cout.setf(ios::fixed|ios::showpoint);
cout << setprecision(2);
cout << token[0] << ' ' << "length="<< token[1] << ' ';
cout << "width=" << token[2] << ' ' ;
cout << "Area=" << areaRectangle << ' ';
cout << "Perimeter=" << periRectangle << '\n';
cout.unsetf(ios::fixed|ios::showpoint);
cout << setprecision(6);
}
else
{
cout << "End of program. Press ENTER to exit.";
cin.ignore(1000,10);
break;
}
}
}
Ok. When your line
const char* token[MAX_TOKENS_PER_LINE] = {0};
creates an array of pointers, but none of them point to anything. The first element is set to 0 (which is a NULL address) and the rest are not initialised. When you run and process line 2 (which has 1 element) token[0] points to 'SQUARE' but token[1] is given the value 0x00 (NULL). This is an invalid memory location. You then process token[1] with the line
length = atof(token[1])?atof(token[1]):0;
and this causes a Segmentation fault because token[1] is a NULL pointer. In my version token[1] is a valid pointer to a NULL string, which sets length to 0. I suggest you compile with the -g flag (eg g++ -g test.cpp -o test). Then call 'gdb test' and use break, run, continue commands to step through the code. You can use the print command to display the contents of variables.
In the first run in gdb just enter 'run'. This will fail, then enter 'bt' which will tell you the failing line, let's call it linenumber.
In the second run enter 'break linenumber' and then 'run' and the execution will stop on the failing line but before it is executed. You can then look at the contents of the variables which will give you a big clue to why it is failing.
Here is some working C++ based closely on your code.
I've revised the I/O handling; fin.getline() reports whether it got a line or not, so it should be used to control the loop; fin.eof() is a red flag warning in my estimation (as is feof(fp) in C).
The core dump occurs because you don't check that you got a length token after the word SQUARE. The revised code checks that it got exactly the correct number of tokens, complaining if not. The code using strtok() has been unified into a single loop; it contains a diagnostic print statement that shows the token just found (valuable for checking what's going on).
I removed a pile of unused variables; each variable is defined and initialized in the calculation blocks.
There are endless possible reservations about using C strings and strtok() in C++ (the printing would be a lot more succinct if all the code were written in C using the C standard I/O functions like printf()). You can find a discussion of the alternatives to strtok() at Strange strtok() error. You can find another discussion on why strtok() is a disaster in a library function at Reading user input and checking the string.
Working code for the 3 lines of data in the question
#include <cstring>
#include <cstdlib>
#include <iostream>
using std::cout;
using std::endl;
using std::cin;
using std::ios;
using std::cerr;
#include<iomanip>
using std::setprecision;
#include <fstream>
using std::ifstream;
const int MAX_CHARS_PER_LINE = 512;
const int MAX_TOKENS_PER_LINE = 20;
const char* const DELIMITER = " ";
int main()
{
// create a file-reading object
const char *fname = "geo.txt";
ifstream fin;
fin.open(fname); // open a file
if (!fin.good())
{
cerr << "Failed to open file " << fname << endl;;
return 1; // exit if file not found
}
// read each line of the file
char buf[MAX_CHARS_PER_LINE];
while (fin.getline(buf, sizeof(buf)))
{
int n = 0;
const char *token[MAX_TOKENS_PER_LINE] = {0};
char *position = buf;
while ((token[n] = strtok(position, DELIMITER)) != 0)
{
cout << "Token " << n << ": " << token[n] << endl;
n++;
position = 0;
}
if (strcmp("SQUARE", token[0]) == 0 && n == 2)
{
float length = atof(token[1])?atof(token[1]):0;
float areaSquare = length * length;
float periSquare = 4 * length;
cout.setf(ios::fixed|ios::showpoint);
cout << setprecision(2);
cout << token[0] << ' ' << "length="<< token[1] << ' ';
cout << "Area=" << areaSquare << ' ';
cout << "Perimeter=" << periSquare << '\n';
cout.unsetf(ios::fixed|ios::showpoint);
cout << setprecision(6);
}
else if (strcmp("RECTANGLE", token[0]) == 0 && n == 3)
{
float length = atof(token[1])?atof(token[1]):0;
float width = atof(token[2])?atof(token[2]):0;
float areaRectangle = length * width;
float periRectangle = 2 * length + 2 * width;
cout.setf(ios::fixed|ios::showpoint);
cout << setprecision(2);
cout << token[0] << ' ' << "length="<< token[1] << ' ';
cout << "width=" << token[2] << ' ' ;
cout << "Area=" << areaRectangle << ' ';
cout << "Perimeter=" << periRectangle << '\n';
cout.unsetf(ios::fixed|ios::showpoint);
cout << setprecision(6);
}
else
{
cout << "Unrecognized data: " << buf << endl;
}
}
}