experchange > cpp

Frederick Gotham (03-03-20, 05:08 PM)
I came across this piece of C++ code yesterday for running a separate program on Linux and getting its stdout. It uses "popen" to start the second program, and later uses "pclose" to close the stream.

unique_ptr<FILE, decltype(&pclose)> stdout_stream(popen("echo Hello World"),"r"), pclose);

I thought it was pretty neat.
Jorgen Grahn (03-03-20, 05:20 PM)
On Tue, 2020-03-03, Frederick Gotham wrote:
> I came across this piece of C++ code yesterday for running a
> separate program on Linux and getting its stdout. It uses "popen" to
> start the second program, and later uses "pclose" to close the
> stream.
> unique_ptr<FILE, decltype(&pclose)> stdout_stream(popen("echo Hello World"),"r"), pclose);
> I thought it was pretty neat.


It shows what you can do with std::unique_ptr so yes it's kind of
neat. But you lose the ability to check the exit status of the other
process, and that's often not acceptable.

/Jorgen
Frederick Gotham (03-03-20, 05:26 PM)
On Tuesday, March 3, 2020 at 3:21:06 PM UTC, Jorgen Grahn wrote:
> On Tue, 2020-03-03, Frederick Gotham wrote:
> It shows what you can do with std::unique_ptr so yes it's kind of
> neat. But you lose the ability to check the exit status of the other
> process, and that's often not acceptable.


It's fine in my case as I only need the string (which can be empty if the second program crashes).

Code taken from stackoverflow:

#include <cstdio> /* In Linux this contains popen, pclose */
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>

std::string exec(char const *const cmd)
{
std::array<char, 128> buffer;

std::string result;

std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);

if ( !pipe )
{
throw std::runtime_error("popen() failed!");
}

while ( nullptr != fgets(buffer.data(), buffer.size(), pipe.get()) )
{
result += buffer.data();
}

return result;
}
Maciej Sobczak (03-04-20, 10:39 AM)
> It's fine in my case as I only need the string (which can be empty if the second program crashes).

> while ( nullptr != fgets(buffer.data(), buffer.size(), pipe.get()) )


And what if the second program crashes in the middle of producing the output? Is the possibility to read partial (corrupted?) output acceptable for you?

This code example is indeed neat, but also a bit deceptive. It makes provisions for the popen failing (and translates it into exception), but ignores the possibility of fgets failing sometime later (also by means other than EOF).
It might be negligible in your particular case (the result might have some integrity features built in its syntax), but is worth being aware of before copy-pasting this code to some other arbitrary system.

But it's true, custom deleters in smart pointers are indeed powerful. :-)
cdalten (03-04-20, 04:02 PM)
On Tuesday, March 3, 2020 at 7:26:55 AM UTC-8, Frederick Gotham wrote:
[..]
> result += buffer.data();
> }
> return result; Ugh...does every other person feel the need to use this idiotic construct? By that way, this can cause undefined behavior.


And with, I'm adding the next person to my killfile who thinks this construct is okay and then tries to justify it with some lame, divorced from reality excuse.
cdalten (03-04-20, 04:04 PM)
On Wednesday, March 4, 2020 at 12:39:56 AM UTC-8, Maciej Sobczak wrote:
> And what if the second program crashes in the middle of producing the output? Is the possibility to read partial (corrupted?) output acceptable for you?
> This code example is indeed neat, but also a bit deceptive. It makes provisions for the popen failing (and translates it into exception), but ignores the possibility of fgets failing sometime later (also by means other than EOF).
> It might be negligible in your particular case (the result might have some integrity features built in its syntax), but is worth being aware of before copy-pasting this code to some other arbitrary system.
> But it's true, custom deleters in smart pointers are indeed powerful. :-)
> --


And the code can also produce undefined behavior. More to the point, I can think of a case where this idiot construct would evaluate to true when the expression is actually false.
cdalten (03-04-20, 04:16 PM)
On Wednesday, March 4, 2020 at 6:05:03 AM UTC-8, cda...@gmail.com wrote:
> On Wednesday, March 4, 2020 at 12:39:56 AM UTC-8, Maciej Sobczak wrote:
> And the code can also produce undefined behavior. More to the point, I can think of a case where this idiot construct would evaluate to true when the expression is actually false.


The ultimate moral is, don't copy and paste things from Stackoverflow unless you know the person's credentials. Because in this case, the person on Stackoverflow who posted this code needs to stay as a Junior Software Developer or the tech support person.
Öö Tiib (03-04-20, 06:25 PM)
On Wednesday, 4 March 2020 16:03:03 UTC+2, cda...@gmail.com wrote:
> On Tuesday, March 3, 2020 at 7:26:55 AM UTC-8, Frederick Gotham wrote:
> Ugh...does every other person feel the need to use this idiotic construct? By that way, this can cause undefined behavior.
> And with, I'm adding the next person to my killfile who thinks this construct is okay and then tries to justify it with some lame, divorced from reality excuse.


What kind of undefined behavior? What construct?
Usage of unique_ptr for simple RAII cleanup is well-known idiom.
No point to make full-blown wrapper on every case.
Launcing sub-processes or opening external communication
channels should not be taken that lightly however. Such
std::unique_ptr<FILE, std::function<void(FILE*)>> feels too
naive there.
cdalten (03-04-20, 07:01 PM)
On Wednesday, March 4, 2020 at 8:25:14 AM UTC-8, Öö Tiib wrote:
> On Wednesday, 4 March 2020 16:03:03 UTC+2, cda...@gmail.com wrote:
> What kind of undefined behavior? What construct?
> Usage of unique_ptr for simple RAII cleanup is well-known idiom.
> No point to make full-blown wrapper on every case.
> Launcing sub-processes or opening external communication
> channels should not be taken that lightly however. Such
> std::unique_ptr<FILE, std::function<void(FILE*)>> feels too
> naive there.


This line...

while ( nullptr != fgets(buffer.data(), buffer.size(), pipe.get()) )

If nullptr returns a value, the entire expression

nullptr != fgets(buffer.data(), buffer.size(), pipe.get())

could get evaluated. Whereas if nullptr was was on the other side, it will throw a runtime error(?). I think that's how it's worded. The only reason Iknow this is because I've dealt with a class of bugs similar to this. And it *always* stems from "inverting" the expression.
Öö Tiib (03-04-20, 08:00 PM)
On Wednesday, 4 March 2020 19:02:36 UTC+2, cda...@gmail.com wrote:
> On Wednesday, March 4, 2020 at 8:25:14 AM UTC-8, Öö Tiib wrote:
> This line...
> while ( nullptr != fgets(buffer.data(), buffer.size(), pipe.get()) )
> If nullptr returns a value, the entire expression
> nullptr != fgets(buffer.data(), buffer.size(), pipe.get())
> could get evaluated.


Uh, I don't follow. The nullptr is a keyword of C++ that can be used
as null pointer constant expression. It was added to make sure that
it can't be implicitly converted to or compared with integer constant
(the issue that NULL had).

The fgets is documented to return null pointer when there was end of
file before reading any characters or error occurred during reading.
So the line is technically just more explicit way to write:

while ( fgets(buffer.data(), buffer.size(), pipe.get()) )

> Whereas if nullptr was was on the other side, it will throw a runtime error(?). I think that's how it's worded. The only reason I know this is because I've dealt with a class of bugs similar to this. And it *always* stems from "inverting" the expression.


How it can throw? The fgets is C library function so noexcept and
inbuilt operator != between two pointers is also noexcept. User
has to call ferror(pipe.get()) to find out if there was error or
not, nothing should throw there.
Frederick Gotham (03-06-20, 01:12 PM)
On Wednesday, March 4, 2020 at 6:00:50 PM UTC, Öö Tiib wrote:

> > If nullptr returns a value, the entire expression
> > nullptr != fgets(buffer.data(), buffer.size(), pipe.get())
> > could get evaluated.

> Uh, I don't follow.


Sometimes misinformation is a little too perfect to be accidental.
cdalten (03-06-20, 03:55 PM)
On Friday, March 6, 2020 at 3:12:15 AM UTC-8, Frederick Gotham wrote:
> On Wednesday, March 4, 2020 at 6:00:50 PM UTC, Öö Tiib wrote:
> > > If nullptr returns a value, the entire expression
> > > nullptr != fgets(buffer.data(), buffer.size(), pipe.get())
> > > could get evaluated.

> > Uh, I don't follow.

> Sometimes misinformation is a little too perfect to be accidental.


If nullptr is on the left hand side, it evaluates to a value, and thus, theentire expression will evaluate right away. However, if nullptr is on the right hand side, it might not evaluate right away. Thus the former can leadto a subtle bug.
James Kuyper (03-06-20, 04:02 PM)
On 3/6/20 6:12 AM, Frederick Gotham wrote:
> On Wednesday, March 4, 2020 at 6:00:50 PM UTC, Öö Tiib wrote:
>>> If nullptr returns a value, the entire expression
>>> nullptr != fgets(buffer.data(), buffer.size(), pipe.get())
>>> could get evaluated.

>> Uh, I don't follow.

> Sometimes misinformation is a little too perfect to be accidental.


You might be right. But that message was posted using Chad's e-mail
address, so I think that amazing amounts of confusion, possibly fueled
by substance abuse, is also a plausible explanation.
James Kuyper (03-06-20, 04:28 PM)
On Friday, March 6, 2020 at 8:55:41 AM UTC-5, cda...@gmail.com wrote:
> On Friday, March 6, 2020 at 3:12:15 AM UTC-8, Frederick Gotham wrote:
> If nullptr is on the left hand side, it evaluates to a value, and thus, the entire expression will evaluate right away. However, if nullptr is on the right hand side, it might not evaluate right away. Thus the former can lead to a subtle bug.


And what consequence do you think evaluation of nullptr will have that
could cause this bug? Keep in mind that evaluation of nullptr does not
(in this context) mean anything different from evaluation of NULL or 0
or (char*)0. That evaluation doesn't actually do anything that could
interfere with anything else.
12345678901234567890123456789012345678901234567890 1234567890123456789012
The evaluation of the left operand of a comparison operator is
indeterminately sequenced with respect to evaluation of the right
operand, and there's no sequence point separating them. Therefore, if
expression1 != expression2 is a problem because it allows the two
expressions to be evaluated in a problematic order, then expression2 !=
expression1 is just as much as problem, because an implementation is
allowed to evaluate them in the same problematic order with either
version of that expression.

But I still have no idea why you think it's a problem.
James Kuyper (03-06-20, 04:29 PM)
On Friday, March 6, 2020 at 8:55:41 AM UTC-5, cda...@gmail.com wrote:
> On Friday, March 6, 2020 at 3:12:15 AM UTC-8, Frederick Gotham wrote:
> If nullptr is on the left hand side, it evaluates to a value, and thus, the entire expression will evaluate right away. However, if nullptr is on the right hand side, it might not evaluate right away. Thus the former can lead to a subtle bug.


And what consequence do you think evaluation of nullptr will have that
could cause this bug? Keep in mind that evaluation of nullptr does not
(in this context) mean anything different from evaluation of NULL or 0
or (char*)0. That evaluation doesn't actually do anything that could
interfere with anything else.

The evaluation of the left operand of a comparison operator is
indeterminately sequenced with respect to evaluation of the right
operand, and there's no sequence point separating them. Therefore, if
expression1 != expression2 is a problem because it allows the two
expressions to be evaluated in a problematic order, then expression2 !=
expression1 is just as much as problem, because an implementation is
allowed to evaluate them in the same problematic order with either
version of that expression.

But I still have no idea why you think it's a problem.

Similar Threads