1

I have a lot of third party functions written in C which expect their input from stdin through scanf(). I have written others functions in C++ which call the c functions. Now, I am preparing the tests and I would like to write the input test cases in C++ strings.

So my question is whether there is a way of expressing that the stdin should read from a C++ string instead of the standard input? If I achieved that, then I could write several tests cases whose inputs are c++ strings, and my C++ functions, which call c functions expecting their input from stdin, would be transparently called.

rwp
  • 1,786
  • 2
  • 17
  • 28
lrleon
  • 2,610
  • 3
  • 25
  • 38
  • Well, you could redirect the input from a file. – Bob__ Apr 05 '18 at 14:09
  • `freopen`? First see how to make a file from a string... No. – user202729 Apr 05 '18 at 14:14
  • 2
    [Related](https://stackoverflow.com/questions/584868/rerouting-stdin-and-stdout-from-c). [Related](https://stackoverflow.com/questions/25325873/redirect-stdout-to-a-variable-in-c). – user202729 Apr 05 '18 at 14:17
  • 1
    If you can rewrite the C functions in C++, write them to accept an argument of type `std::istream &`. There are various types of `istream` (`ifstream` reads from a file, `istringstream` reads from a string, etc). All you would need to do to test your functions is provide an instance of an appropriate type which has been initialised appropriately. Generally speaking, C functions that read from `stdin` can't be coerced to read from a string in standard C++. – Peter Apr 05 '18 at 14:20
  • There is `c_str`. The second related link above only works for Linux (POSIX?) but that may be what you need. – user202729 Apr 05 '18 at 14:23
  • You could use `std::cin` and `std::string`. In C++, the `cin` is tied to the standard input. – Thomas Matthews Apr 05 '18 at 16:11
  • Possible duplicate of [Setting a fmemopen ed file descriptor to be the standard input for a child process](https://stackoverflow.com/questions/7316775/setting-a-fmemopen-ed-file-descriptor-to-be-the-standard-input-for-a-child-proce) – Lanting Apr 06 '18 at 09:50
  • [https://stackoverflow.com/questions/28770964/is-it-possible-to-fake-a-file-stream-such-as-stdin-in-c](Is it possible to fake a file stream, such as stdin, in C?) – Lanting Apr 06 '18 at 09:54

1 Answers1

3

I'm not sure how portable this is, but at least with gcc-6.3 on Linux, you can reassign stdin to point to different stream, which scanf() will then use transparently as though it is still reading from the terminal. If you want to read from a pre-existing string, that new stream can be opened with something like fmemopen(), which should be available on POSIX systems. This allows you to create a FILE* from a block of memory, such as the contents of a std::string.

As an illustration, the following code will scanf() five values from the string "String with 3.14159 * 2" as though they had been entered from the terminal:

#include <cstdio>
#include <iostream>
#include <string>
#include <unistd.h>

int main(int argc, char *argv[])
{ const std::string input("String with 3.14159 * 2");
  char s0[16], s1[16], s2[8];
  double pi = 3; 
  int two = -2;

  { FILE *old_stdin = stdin;
    FILE* strm = fmemopen((void*)input.c_str(), input.size(), "r");
    stdin = strm;

    scanf("%s %s %lf %s %d", s0, s1, &pi, s2, &two);
    std::cout << "s0=\"" << s0 << "\" s1=\"" << s1 << "\""
              << " pi=" << pi << " s2=\"" << s2 << "\""
              << " two=" << two << std::endl;

    stdin = old_stdin;
    fclose(strm);
  }

  scanf("%12s", s0);
  std::cout << "Stdin: \"" << s0 << "\"" << std::endl;

  return 0;
}

This produces the following output:

s0="String" s1="with" pi=3.14159 s2="*" two=2

before returning stdin to its normal behaviour, where the second scanf() waits for input from the terminal.

It would be tempting to try a similar approach using dup2() (as used here), but it appears that the file-descriptor returned by invoking fileno() on the value returned from fmemopen() is not valid (being -1 on my system).

rwp
  • 1,786
  • 2
  • 17
  • 28