Call a c program from C++ and pass arguments - c++

I have a c++ program, and at some point in my program I need to call a c program and pass some arguments to it.
I am working in linux env.
the file simpsh is a compiled c file in the same dir.
resulting_simpsh_command is a string with data of this type
--creat --trunc --wronly f1 and so far.
When I check the values that I recieve in C program, it shows this instead
void execute_simpsh(string resulting_simpsh_command)
{
pid_t pid = fork();
if(pid == -1)
perror("Fork failed!");
else if(pid ==0)
{
char* args[256];
string simpsh ="./simpsh";
args [0] = (char*) simpsh.c_str();
string temp_option_holder="";
int nextCommand=1;
for(int i=0;i<resulting_simpsh_command.length();i++)
{
if(resulting_simpsh_command[i] !=' ')
{
temp_option_holder += resulting_simpsh_command[i];
}
else
{
cout<<"saving to argument: "<<temp_option_holder<<endl;
args [nextCommand] = (char*) temp_option_holder.c_str();
temp_option_holder="";
nextCommand +=1;
}
}
cout<<"command numbers "<<nextCommand<<endl;
args [nextCommand + 1] = NULL;
if(execvp(args[0],args) == -1)
cout<<"Failed to open simpsh, maybe you didnt compile?"<<endl;
exit(1);
}
else
{
//not important for now
}
}

A lot of the args array will be invalid pointers due to your (mis)use of c_str.
The pointer becomes invalid as soon as the string's buffer is reallocated.
Some of the args pointers could also point to the same thing even though they're valid.
Two options for fixing this (off the top of my head):
Dynamic allocation:
args[nextCommand] = strdup(temp_option_holder.c_str());
which requires deallocation later.
Or, if you can live with limiting the argument length, you can use another array and avoid the memory management (but then you need to worry about overflows instead):
char arg_strings[256][ARGUMENT_LENGTH] = {0};
char* args[256] = {0};
// ...
assert(temp_option_holder.length() < ARGUMENT_LENGTH);
strncpy(arg_strings[nextCommand], temp_option_holder.c_str(), ARGUMENT_LENGTH);
args[nextCommand] = arg_strings[nextCommand];

C++ has so many nice things beyond std::cout. Use them!
The core of this solution is std::replace()ing the spaces in your command line with zero bytes, then taking pointers to the beginning of each (C) string in the command line (conveniently using the zero bytes from the replace() as terminators), pushing those pointers into a vector we will then pass to execve().
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
// Usually I would recommend using const std::string &
// to avoid the copy, but we are going to do violence
// on the argument so we really *want* a writeable copy.
void execute_simpsh( string cmd )
{
pid_t pid = fork();
if ( pid == -1 )
{
perror("Fork failed!");
}
else if ( pid == 0 )
{
// This will hold our pointers -- and is not limited
// to a maximum amount other than available RAM
std::vector< char * > args( { "./simpsh" } );
if ( ! cmd.empty() )
{
// Tokenize -- std::string may contain null chars!
std::replace( cmd.begin(), cmd.end(), ' ', '\0' );
// std::string is zero-terminated in memory, but that
// zero char is not considered by .find() so we add one,
// making the loop below easier.
cmd.append( '\0' );
size_t i = 0;
// Gather pointers to each argument. find_first_not_of()
// skips leading (and embedded) ex-spaces.
while ( ( i = cmd.find_first_not_of( '\0', i ) ) != std::string::npos )
{
args.push_back( cmd.data() + i );
// find() skips to the end of the current argument.
i = cmd.find( '\0', i );
}
}
std::cout << "Tokens: " << args.size() << "\n";
// args.data() is std::vector's underlying array
// of the pointers we pushed. How convenient...
for ( auto token : args.data() )
{
std::cout << " " << token << "\n";
}
// ...as "array of char *" is exactly what we need.
if ( execvp( args[0], args.data() ) == -1 )
{
std::cout << "Failed to open simpsh" << std::endl;
}
std::exit( 1 );
}
else
{
// ...
}
}
Note that this solution will mercilessly clobber any spaces in your command line, including those in quotation marks, and does not handle newlines, tabs etc. -- then again, your original solution did not behave differently either, so I will leave extending the capabilities to you.

Related

why does the function skip the padding on the first call?

#include <iostream>
#include <cstring>
#include <cctype>
void go_lower(char *str);
void fill_str(char *mass);
int main()
{
char str_1[80];
char str_2[80];
char *pointer_1;
char *pointer_2;
pointer_1 = str_1;
pointer_2 = str_2;
fill_str(pointer_1);
fill_str(pointer_2);
if(strcmp(str_1, str_2))
{
std::cout << "This strings are equal\n";
}
else
{
std::cout << "This strings are not equal\n";
}
return 0;
}
void fill_str(char *mass)
{
std::cout << "Insert string to compare\n";
while(*mass)
{
std::cin >> *mass;
mass++;
}
std::cout << '\n';
}
void go_lower(char *str)
{
while(*str)
{
if(isupper(*str))
{
*str = tolower(*str);
}
str++;
}
}
Not so long ago I started studying programming and I try to understand the test tasks. In this program, I need to compare strings regardless of the case of the characters. when the program starts, it goes straight to filling in str_2 and the comparison block concludes that the strings are equal. What is the error?
While in C++, you should really use std::basic_string, you still need to know how to handle plain-old-arrays. When passing an plain-old-array to a function to be filled, you must pass an additional parameter to provide the array size to the function. Within the function you can only read size - 2 characters into the array (saving index size - 1 for the nul-terminating character '\0' (ASCII 0).
You should also avoid using MagicNumbers in your code. Instead, if you need a constant (such as for 80), #define a constant or use a const int in C++. Since you have a choice for your return type, you should return the number of characters read into each string. The information will be available when you leave your fill_str() function and there is no reason to have to calling function re-scan for end-of-string.
You cannot use std::cin effectively to read multi-word input in a function as you are attempting. std::cin discards whitespace, so you will never read the '\n' generated by the user pressing [Enter]. So you have know way to know when the user is done typing. Instead you must use std::cin.get(); which will read every character.
With those changes, you can write your function to use plain-old-arrays as follows:
#define MAXC 80 /* if you need a constant, #define one (or more) */
...
/* using plain-old-arrays, you must pass size of array */
size_t fill_str (char *mass, size_t size)
{
size_t n = 0; /* length counter */
std::cout << "\nInsert string to compare\n";
while (n < size - 1) /* read while array not full */
{
if ((*mass = std::cin.get()) && *mass != '\n') /* use get() to read whitespace */
n++; /* increment length counter */
if (*mass == '\n') /* if \n user pressed [Enter], input complete */
break;
mass++; /* increment pointer */
}
*mass = 0; /* nul-terminate when you leave read-loop */
return n; /* return length */
}
Your example, omitting your unused go_lower() function, could then be:
#include <iostream>
#include <cstring>
#define MAXC 80 /* if you need a constant, #define one (or more) */
size_t fill_str (char *mass, size_t size);
int main (void)
{
char str_1[MAXC];
char str_2[MAXC];
char *pointer_1;
char *pointer_2;
size_t len_1, len_2;
pointer_1 = str_1;
pointer_2 = str_2;
len_1 = fill_str (pointer_1, MAXC);
len_2 = fill_str (pointer_2, MAXC);
if (len_1 != len_2) {
std::cout << "\nstrings differ in length (" <<
len_1 << " != " << len_2 << ").\n";
return 0;
}
if (strcmp (str_1, str_2) == 0) /* only equal if return is 0 */
{
std::cout << "\nThis strings are equal\n";
}
else
{
std::cout << "\nThis strings are not equal\n";
}
return 0;
}
/* using plain-old-arrays, you must pass size of array */
size_t fill_str (char *mass, size_t size)
{
size_t n = 0; /* length counter */
std::cout << "\nInsert string to compare\n";
while (n < size - 1) /* read while array not full */
{
if ((*mass = std::cin.get()) && *mass != '\n') /* use get() to read whitespace */
n++; /* increment length counter */
if (*mass == '\n') /* if \n user pressed [Enter], input complete */
break;
mass++; /* increment pointer */
}
*mass = 0; /* nul-terminate when you leave read-loop */
return n; /* return length */
}
Example Use/Output
$ ./bin/golower
Insert string to compare
apples and bananas
Insert string to compare
apples and bananas
This strings are equal
$ ./bin/golower
Insert string to compare
pickle
Insert string to compare
pickles
strings differ in length (6 != 7).
As mentioned at the beginning, you should really use std::string and getline() to read the user input, but you should also be familiar with plain-old-arrays. Look things over and let me know if you have questions.
The while (*mass) in your fill_str function will not loop even once because * mass may be initialized to 0 here (it is actually undefined behavior and up to your compiler). Because of this none of your strings will ever get filled with anything.
strcmp evaluates your strings equal because they are initialized in such a way they are equal from the beginning and are never changed because of the reason stated above.
To read a string consider using getline or std::cin if you know that your string will not contain spaces, eg.
std::cin >> mass;
See more here https://www.cplusplus.com/doc/tutorial/basic_io. Also consider using std::string instead of char arrays.

put the file into an array in c++

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";
}

A local array repeats inside a loop! C++

The current_name is a local char array inside the following loop. I declared it inside the loop so it changes every time I read a new line from a file. But, for some reason the previous data is not removed from the current_name! It prints old data out if it wasn't overridden by new characters from the next line.
ANY IDEAS?
while (isOpen && !file.eof()) {
char current_line[LINE];
char current_name[NAME];
file.getline(current_line, LINE);
int i = 0;
while (current_line[i] != ';') {
current_name[i] = current_line[i];
i++;
}
cout << current_name << endl;
}
You're not terminating current_name after filling it. Add current_name[i] = 0 after the inner loop just before your cout. You're probably seeing this if you read abcdef then read jkl and probably get jkldef for output
UPDATE
You wanted to know if there is a better way. There is--and we'll get to it. But, coming from Java, your question and followup identified some larger issues that I believe you should be aware of. Be careful what you wish for--you may actually get it [and more] :-). All of the following is based on love ...
Attention All Java Programmers! Welcome to "A Brave New World"!
Basic Concepts
Before we even get to C the language, we need to talk about a few concepts first.
Computer Architecture:
https://en.wikipedia.org/wiki/Computer_architecture
https://en.wikipedia.org/wiki/Instruction_set
Memory Layout of Computer Programs:
http://www.geeksforgeeks.org/memory-layout-of-c-program/
Differences between Memory Addresses/Pointers and Java References:
Is Java "pass-by-reference" or "pass-by-value"?
https://softwareengineering.stackexchange.com/questions/141834/how-is-a-java-reference-different-from-a-c-pointer
Concepts Alien to Java Programmers
The C language gives you direct access the underlying computer architecture. It will not do anything that you don't explicitly specify. Herein, I'm mentioning C [for brevity] but what I'm really talking about is a combination of the memory layout and the computer architecture.
If you read memory that you didn't initialize, you will see seemingly random data.
If you allocate something from the heap, you must explicitly free it. It doesn't magically get marked for deletion by a garbage collector when it "goes out of scope".
There is no garbage collector in C
C pointers are far more powerful that Java references. You can add and subtract values to pointers. You can subtract two pointers and use the difference as an index value. You can loop through an array without using index variables--you just deference a pointer and increment the pointer.
The data of automatic variables in Java are stored in the heap. Each variable requires a separate heap allocation. This is slow and time consuming.
In C, the data of automatic variables in stored in the stack frame. The stack frame is a contiguous area of bytes. To allocate space for the stack frame, C simply subtracts the desired size from the stack pointer [hardware register]. The size of the stack frame is the sum of all variables within a given function's scope, regardless of whether they're declared inside a loop inside the function.
Its initial value depends upon what previous function used that area for and what byte values it stored there. Thus, if main calls function fnca, it will fill the stack with whatever data. If then main calls fncb it will see fnca's values, which are semi-random as far as fncb is concerned. Both fnca and fncb must initialize stack variables before they are used.
Declaration of a C variable without an initializer clause does not initialize the variable. For the bss area, it will be zero. For a stack variable, you must do that explicitly.
There is no range checking of array indexes in C [or pointers to arrays or array elements for that matter]. If you write beyond the defined area, you will write into whatever has been mapped/linked into the memory region next. For example, if you have a memory area: int x[10]; int y; and you [inadvertently] write to x[10] [one beyond the end] you will corrupt y
This is true regardless of which memory section (e.g. data, bss, heap, or stack) your array is in.
C has no concept of a string. When people talk about a "c string" what they're really talking about is a char array that has an "end of string" (aka EOS) sentinel character at the end of the useful data. The "standard" EOS char is almost universally defined as 0x00 [since ~1970]
The only intrinsic types supported by an architecture are: char, short, int, long/pointer, long long, and float/double. There may be some others on a given arch, but that's the usual list. Everything else (e.g. a class or struct is "built up" by the compiler as a convenience to the programmer from the arch intrinsic types)
Here are some things that are about C [and C++]:
- C has preprocessor macros. Java has no concept of macros. Preprocessor macros can be thought of as a crude form of metaprogramming.
- C has inline functions. They look just like regular functions, but the compiler will attempt to insert their code directly into any function that calls one. This is handy if the function is cleanly defined but small (e.g. a few lines). It saves the overhead of actually calling the function.
Examples
Here are several versions of your original program as an example:
// myfnc1 -- original
void
myfnc1(void)
{
istream file;
while (isOpen && !file.eof()) {
char current_line[LINE];
char current_name[NAME];
file.getline(current_line, LINE);
int i = 0;
while (current_line[i] != ';') {
current_name[i] = current_line[i];
i++;
}
current_name[i] = 0;
cout << current_name << endl;
}
}
// myfnc2 -- moved definitions to function scope
void
myfnc2(void)
{
istream file;
int i;
char current_line[LINE];
char current_name[NAME];
while (isOpen && !file.eof()) {
file.getline(current_line, LINE);
i = 0;
while (current_line[i] != ';') {
current_name[i] = current_line[i];
i++;
}
current_name[i] = 0;
cout << current_name << endl;
}
}
// myfnc3 -- converted to for loop
void
myfnc(void)
{
istream file;
int i;
char current_line[LINE];
char current_name[NAME];
while (isOpen && !file.eof()) {
file.getline(current_line, LINE);
for (i = 0; current_line[i] != ';'; ++i)
current_name[i] = current_line[i];
current_name[i] = 0;
cout << current_name << endl;
}
}
// myfnc4 -- converted to use pointers
void
myfnc4(void)
{
istream file;
const char *line;
char *name;
char current_line[LINE];
char current_name[NAME];
while (isOpen && !file.eof()) {
file.getline(current_line, LINE);
name = current_name;
for (line = current_line; *line != ';'; ++line, ++name)
*name = *line;
*name = 0;
cout << current_name << endl;
}
}
// myfnc5 -- more efficient use of pointers
void
myfnc5(void)
{
istream file;
const char *line;
char *name;
int chr;
char current_line[LINE];
char current_name[NAME];
while (isOpen && !file.eof()) {
file.getline(current_line, LINE);
name = current_name;
line = current_line;
for (chr = *line++; chr != ';'; chr = *line++, ++name)
*name = chr;
*name = 0;
cout << current_name << endl;
}
}
// myfnc6 -- fixes bug if line has no semicolon
void
myfnc6(void)
{
istream file;
const char *line;
char *name;
int chr;
char current_line[LINE];
char current_name[NAME];
while (isOpen && !file.eof()) {
file.getline(current_line, LINE);
name = current_name;
line = current_line;
for (chr = *line++; chr != 0; chr = *line++, ++name) {
if (chr == ';')
break;
*name = chr;
}
*name = 0;
cout << current_name << endl;
}
}
// myfnc7 -- recoded to use "smart" string
void
myfnc7(void)
{
istream file;
const char *line;
char *name;
int chr;
char current_line[LINE];
xstr_t current_name;
xstr_t *name;
name = &current_name;
xstrinit(name);
while (isOpen && !file.eof()) {
file.getline(current_line, LINE);
xstragain(name);
line = current_line;
for (chr = *line++; chr != 0; chr = *line++) {
if (chr == ';')
break;
xstraddchar(name,chr);
}
cout << xstrcstr(name) << endl;
}
xstrfree(name);
}
Here is a "smart" string [buffer] class similar to what you're used to:
// xstr -- "smart" string "class" for C
typedef struct {
size_t xstr_maxlen; // maximum space in string buffer
char *xstr_lhs; // pointer to start of string
char *xstr_rhs; // pointer to start of string
} xstr_t;
// xstrinit -- reset string buffer
void
xstrinit(xstr_t *xstr)
{
memset(xstr,0,sizeof(xstr));
}
// xstragain -- reset string buffer
void
xstragain(xstr_t xstr)
{
xstr->xstr_rhs = xstr->xstr_lhs;
}
// xstrgrow -- grow string buffer
void
xstrgrow(xstr_t *xstr,size_t needlen)
{
size_t curlen;
size_t newlen;
char *lhs;
lhs = xstr->xstr_lhs;
// get amount we're currently using
curlen = xstr->xstr_rhs - lhs;
// get amount we'll need after adding the whatever
newlen = curlen + needlen + 1;
// allocate more if we need it
if ((newlen + 1) >= xstr->xstr_maxlen) {
// allocate what we'll need plus a bit more so we're not called on
// each add operation
xstr->xstr_maxlen = newlen + 100;
// get more memory
lhs = realloc(lhs,xstr->xstr_maxlen);
xstr->xstr_lhs = lhs;
// adjust the append pointer
xstr->xstr_rhs = lhs + curlen;
}
}
// xstraddchar -- add character to string
void
xstraddchar(xstr_t *xstr,int chr)
{
// get more space in string buffer if we need it
xstrgrow(xstr,1);
// add the character
*xstr->xstr_rhs++ = chr;
// maintain the sentinel/EOS as we go along
*xstr->xstr_rhs = 0;
}
// xstraddstr -- add string to string
void
xstraddstr(xstr_t *xstr,const char *str)
{
size_t len;
len = strlen(str);
// get more space in string buffer if we need it
xstrgrow(xstr,len);
// add the string
memcpy(xstr->xstr_rhs,str,len);
*xstr->xstr_rhs += len;
// maintain the sentinel/EOS as we go along
*xstr->xstr_rhs = 0;
}
// xstrcstr -- get the "c string" value
char *
xstrcstr(xstr_t *xstr,int chr)
{
return xstr->xstr_lhs;
}
// xstrfree -- release string buffer data
void
xstrfree(xstr_t *xstr)
{
char *lhs;
lhs = xstr->xstr_lhs;
if (lhs != NULL)
free(lhs);
xstrinit(xstr);
}
Recommendations
Before you try to "get around" a "c string", embrace it. You'll encounter it in many places. It's unavoidable.
Learn how to manipulate pointers as easily as index variables. They're more flexible and [once you get the hang of them] easier to use. I've seen code written by programmers who didn't learn this and their code is always more complex than it needs to be [and usually full of bugs that I've needed to fix].
Good commenting is important in any language but, perhaps, more so in C than Java for certain things.
Always compile with -Wall -Werror and fix any warnings. You have been warned :-)
I'd play around a bit with the myfnc examples I gave you. This can help.
Get a firm grasp of the basics before you ...
And now, a word about C++ ...
Most of the above was about architecture, memory layout, and C. All of that still applies to C++.
C++ does do a more limited reclamation of stack variables when the function returns and they go out of scope. This has its pluses and minuses.
C++ has many classes to alleviate the tedium of common functions/idioms/boilerplate. It has the std standard template library. It also has boost. For example, std::string will probably do what you want. But, compare it against my xstr first.
But, once again, I wish to caution you. At your present level, work from the fundamentals, not around them.
Adding current_name[i] = 0; as described did not work for me.
Also, I got an error on isOpen as shown in the question.
Therefore, I freehanded a revised program beginning with the code presented in the question, and making adjustments until it worked properly given an input file having two rows of text in groups of three alpha characters that were delimited with " ; " without the quotes. That is, the delimiting code was space, semicolon, space. This code works.
Here is my code.
#define LINE 1000
int j = 0;
while (!file1.eof()) {
j++;
if( j > 20){break;} // back up escape for testing, in the event of an endless loop
char current_line[LINE];
//string current_name = ""; // see redefinition below
file1.getline(current_line, LINE, '\n');
stringstream ss(current_line); // stringstream works better in this case
while (!ss.eof()) {
string current_name;
ss >> current_name;
if (current_name != ";")
{
cout << current_name << endl;
} // End if(current_name....
} // End while (!ss.eof...
} // End while(!file1.eof() ...
file1.close();
cout << "Done \n";

reading last n lines from file in c/c++

I have seen many posts but didn't find something like i want.
I am getting wrong output :
ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ...... // may be this is EOF character
Going into infinite loop.
My algorithm:
Go to end of file.
decrease position of pointer by 1 and read character by
character.
exit if we found our 10 lines or we reach beginning of file.
now i will scan the full file till EOF and print them //not implemented in code.
code:
#include<iostream>
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;
int main()
{
FILE *f1=fopen("input.txt","r");
FILE *f2=fopen("output.txt","w");
int i,j,pos;
int count=0;
char ch;
int begin=ftell(f1);
// GO TO END OF FILE
fseek(f1,0,SEEK_END);
int end = ftell(f1);
pos=ftell(f1);
while(count<10)
{
pos=ftell(f1);
// FILE IS LESS THAN 10 LINES
if(pos<begin)
break;
ch=fgetc(f1);
if(ch=='\n')
count++;
fputc(ch,f2);
fseek(f1,pos-1,end);
}
return 0;
}
UPD 1:
changed code: it has just 1 error now - if input has lines like
3enil
2enil
1enil
it prints 10 lines only
line1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2
line3ÿine1
line2
PS:
1. working on windows in notepad++
this is not homework
also i want to do it without using any more memory or use of STL.
i am practicing to improve my basic knowledge so please don't post about any functions (like tail -5 tc.)
please help to improve my code.
Comments in the code
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *in, *out;
int count = 0;
long int pos;
char s[100];
in = fopen("input.txt", "r");
/* always check return of fopen */
if (in == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
out = fopen("output.txt", "w");
if (out == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
fseek(in, 0, SEEK_END);
pos = ftell(in);
/* Don't write each char on output.txt, just search for '\n' */
while (pos) {
fseek(in, --pos, SEEK_SET); /* seek from begin */
if (fgetc(in) == '\n') {
if (count++ == 10) break;
}
}
/* Write line by line, is faster than fputc for each char */
while (fgets(s, sizeof(s), in) != NULL) {
fprintf(out, "%s", s);
}
fclose(in);
fclose(out);
return 0;
}
There are a number of problems with your code. The most
important one is that you never check that any of the functions
succeeded. And saving the results an ftell in an int isn't
a very good idea either. Then there's the test pos < begin;
this can only occur if there was an error. And the fact that
you're putting the results of fgetc in a char (which results
in a loss of information). And the fact that the first read you
do is at the end of file, so will fail (and once a stream enters
an error state, it stays there). And the fact that you can't
reliably do arithmetic on the values returned by ftell (except
under Unix) if the file was opened in text mode.
Oh, and there is no "EOF character"; 'ÿ' is a perfectly valid
character (0xFF in Latin-1). Once you assign the return value
of fgetc to a char, you've lost any possibility to test for
end of file.
I might add that reading backwards one character at a time is
extremely inefficient. The usual solution would be to allocate
a sufficiently large buffer, then count the '\n' in it.
EDIT:
Just a quick bit of code to give the idea:
std::string
getLastLines( std::string const& filename, int lineCount )
{
size_t const granularity = 100 * lineCount;
std::ifstream source( filename.c_str(), std::ios_base::binary );
source.seekg( 0, std::ios_base::end );
size_t size = static_cast<size_t>( source.tellg() );
std::vector<char> buffer;
int newlineCount = 0;
while ( source
&& buffer.size() != size
&& newlineCount < lineCount ) {
buffer.resize( std::min( buffer.size() + granularity, size ) );
source.seekg( -static_cast<std::streamoff>( buffer.size() ),
std::ios_base::end );
source.read( buffer.data(), buffer.size() );
newlineCount = std::count( buffer.begin(), buffer.end(), '\n');
}
std::vector<char>::iterator start = buffer.begin();
while ( newlineCount > lineCount ) {
start = std::find( start, buffer.end(), '\n' ) + 1;
-- newlineCount;
}
std::vector<char>::iterator end = remove( start, buffer.end(), '\r' );
return std::string( start, end );
}
This is a bit weak in the error handling; in particular, you
probably want to distinguish the between the inability to open
a file and any other errors. (No other errors should occur,
but you never know.)
Also, this is purely Windows, and it supposes that the actual
file contains pure text, and doesn't contain any '\r' that
aren't part of a CRLF. (For Unix, just drop the next to the
last line.)
This can be done using circular array very efficiently.
No additional buffer is required.
void printlast_n_lines(char* fileName, int n){
const int k = n;
ifstream file(fileName);
string l[k];
int size = 0 ;
while(file.good()){
getline(file, l[size%k]); //this is just circular array
cout << l[size%k] << '\n';
size++;
}
//start of circular array & size of it
int start = size > k ? (size%k) : 0 ; //this get the start of last k lines
int count = min(k, size); // no of lines to print
for(int i = 0; i< count ; i++){
cout << l[(start+i)%k] << '\n' ; // start from in between and print from start due to remainder till all counts are covered
}
}
Please provide feedback.
int end = ftell(f1);
pos=ftell(f1);
this tells you the last point at file, so EOF.
When you read, you get the EOF error, and the ppointer wants to move 1 space forward...
So, i recomend decreasing the current position by one.
Or put the fseek(f1, -2,SEEK_CUR) at the beginning of the while loop to make up for the fread by 1 point and go 1 point back...
I believe, you are using fseek wrong. Check man fseek on the Google.
Try this:
fseek(f1, -2, SEEK_CUR);
//1 to neutrialize change from fgect
//and 1 to move backward
Also you should set position at the beginning to the last element:
fseek(f1, -1, SEEK_END).
You don't need end variable.
You should check return values of all functions (fgetc, fseek and ftell). It is good practise. I don't know if this code will work with empty files or sth similar.
Use :fseek(f1,-2,SEEK_CUR);to back
I write this code ,It can work ,you can try:
#include "stdio.h"
int main()
{
int count = 0;
char * fileName = "count.c";
char * outFileName = "out11.txt";
FILE * fpIn;
FILE * fpOut;
if((fpIn = fopen(fileName,"r")) == NULL )
printf(" file %s open error\n",fileName);
if((fpOut = fopen(outFileName,"w")) == NULL )
printf(" file %s open error\n",outFileName);
fseek(fpIn,0,SEEK_END);
while(count < 10)
{
fseek(fpIn,-2,SEEK_CUR);
if(ftell(fpIn)<0L)
break;
char now = fgetc(fpIn);
printf("%c",now);
fputc(now,fpOut);
if(now == '\n')
++count;
}
fclose(fpIn);
fclose(fpOut);
}
I would use two streams to print last n lines of the file:
This runs in O(lines) runtime and O(lines) space.
#include<bits/stdc++.h>
using namespace std;
int main(){
// read last n lines of a file
ifstream f("file.in");
ifstream g("file.in");
// move f stream n lines down.
int n;
cin >> n;
string line;
for(int i=0; i<k; ++i) getline(f,line);
// move f and g stream at the same pace.
for(; getline(f,line); ){
getline(g, line);
}
// g now has to go the last n lines.
for(; getline(g,line); )
cout << line << endl;
}
A solution with a O(lines) runtime and O(N) space is using a queue:
ifstream fin("file.in");
int k;
cin >> k;
queue<string> Q;
string line;
for(; getline(fin, line); ){
if(Q.size() == k){
Q.pop();
}
Q.push(line);
}
while(!Q.empty()){
cout << Q.front() << endl;
Q.pop();
}
Here is the solution in C++.
#include <iostream>
#include <string>
#include <exception>
#include <cstdlib>
int main(int argc, char *argv[])
{
auto& file = std::cin;
int n = 5;
if (argc > 1) {
try {
n = std::stoi(argv[1]);
} catch (std::exception& e) {
std::cout << "Error: argument must be an int" << std::endl;
std::exit(EXIT_FAILURE);
}
}
file.seekg(0, file.end);
n = n + 1; // Add one so the loop stops at the newline above
while (file.tellg() != 0 && n) {
file.seekg(-1, file.cur);
if (file.peek() == '\n')
n--;
}
if (file.peek() == '\n') // If we stop in the middle we will be at a newline
file.seekg(1, file.cur);
std::string line;
while (std::getline(file, line))
std::cout << line << std::endl;
std::exit(EXIT_SUCCESS);
}
Build:
$ g++ <SOURCE_NAME> -o last_n_lines
Run:
$ ./last_n_lines 10 < <SOME_FILE>

c++ read content of a pointer

i'm new in c++ world, i just use it for litle app that help me in my work, now, i need to read the content of a folder, list the folder content, i've made a function that return a pointer with the name of every obj in the folder, but now, i don't know how to read the content of the pointer to just print it in a console, my function look like this
string* listdir (const char *path)
{
string* result = new string[50]; // limit to 50 obj
DIR *pdir = NULL;
pdir = opendir (path);
struct dirent *pent = NULL;
if (pdir == NULL)
{
printf ("\nERROR! pdir could not be initialised correctly");
return NULL;
}
int i = 0;
while (pent = readdir (pdir))
{
if (pent == NULL)
{
printf ("\nERROR! pent could not be initialised correctly");
return NULL;
}
//printf ("%s\n", pent->d_name);
result[i++]= pent->d_name;
}
closedir (pdir);
return result;
}
i've been trying to print the result of teh function
int main()
{
string *dirs;
dirs = listdir("c:\\");
int i = 0;
//while(dirs[i])
//{
//cout<<dirs[i]<<'\n';
//++i;
//}
}
but i really don't know what i'm doing, lol, some help would be perfect
thanks
Examine your while loop condition : dirs[i] is a std::string. You are using a string object in a boolean context : would you expect std::string to convert to bool ?
My recommendation : ditch the fixed sized array and go for std::vector.
void listdir(const char *path, std::vector<std::string> &dirs)
{
/* ... */
while (pent = readdir (pdir))
{
/* ... */
dirs.push_back(pent->d-name);
}
closedir(pdir);
}
int main()
{
std::vector<std::string> dirs;
listdir("c:\\", dirs);
for (std::vector<std::string>::const_iterator it = dirs.begin(), end = dirs.end(); it != end; ++it)
std::cout << *it << std::endl;
}
int main()
{
string *dirs;
dirs = listdir("c:\\");
for (int i = 0; i < 50 && dirs[i].size() > 0; ++i)
{
cout << dirs[i] << '\n';
}
}
dirs is a pointer to an array, so you can index it like an array. You created an array of 50, so you need to limit yourself to 50 here too. Since you might not have populated the whole array, the .size() check allows the printing loop to stop early.
There is some major confusion in your code, especially between arrays of characters, strings and arrays of strings. Also, there is a memory leak.
Here are my questions / concerns:
Issues / Concerns
The opendir function may be called
with a null parameter. You should
check for a null path before calling
opendir.
Returns NULL after declaring some
variables. IMHO, one should check
parameters before declaring
variables.
How does one know how many valid
entries are in the returned array?
If the size of the array (known only
to the listdir function) changes,
the users of the function are
doomed.
Is the type of pent->d_name the
same as string *?
The address of the directory name,
pent->d_name, is copied into the
results array, but not the content
of the directory name. The OS may
reuse this location without telling
you; so copying the address of the
text is not a good idea.
The main function does not delete
the memory allocated for the
results. This is known as a memory
leak.
Suggestions / Fixes
Use std::string within the
function. This takes care of
allocating memory for text.
Use std::vector<string> for the
results. This takes care of knowing
the quantity of directories and no
need to dynamically allocate or
deallocate memory.
Create a std::string from the
pent->d_name and use push_back
to append the string to the results.
In C++, dereferencing a pointer is achieved using the * operator, just like in 'C'.
However, there are a number of problems with your code, which I have addressed here because I was bored...
#include <string>
#include <iostream>
#include <list>
#include <dirent.h>
typedef std::list<std::string> dir_list;
bool listdir(const std::string& path, dir_list& result)
{
bool retval = true;
DIR* pdir = opendir(path.c_str());
if (pdir == NULL)
{
std::cerr << "ERROR! pdir could not be initialised correctly" << std::endl;;
retval = false;
}
else
{
for (dirent* pent = readdir(pdir); pent != NULL; pent = readdir(pdir))
{
if (pent == NULL && result.empty())
{
std::cerr << "ERROR! pent could not be initialised correctly" << std::endl;
retval = false;
}
if (result.size() < 50)
{// *really* limit to 50!
result.push_back(pent->d_name);
}
}
closedir(pdir);
}
return retval;
}
int main()
{
dir_list dirs;
if (listdir("C:/", dirs))
{
for (dir_list::const_iterator iter(dirs.begin()), end(dirs.end()); iter != end; ++iter)
{
std::cout << *iter << std::endl;
}
}
}
Since you're using C++, STL, its string and container classes will save you a World of pointer pain!
your while statement looks strange, it looks like it's expecting dirs[i] to be a pointer, but I believe dirs[i] will be a non-pointer type. Maybe change it to (assuming string is std::string):
while(i < 50 && dirs[i].length() > 0)
{
cout<<dirs[i]<<'\n';
++i;
}