1

When using fscanf to read a string and store it into to a string variable, fscanf expects a char* as the parameter following the format string, .i.e., this is how it should be (as far as I know):

char str[<appropriate length here>];
std::fscanf(filepointer,"%s\n",str);

If I have std::string str instead of char str[] i get a -Wformat warning (obviously), and the program crashes with a Segnetation fault (i dont' know why, yet, but i'm not surprised). Aside from fixing the segfault i'd also do this properly, i.e., without receiving -Wformat warnings.

Is there a way to do the char* to std::string conversion "inline", i.e., inside the parameter list of fscanf or would I have to create a temporary char* variable to store the data and convert it to a std::string afterwards (as described, e.g., here, and I don't see a way to apply this method to my case).

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 2
    The `scanf` family of functions are inherited from C and have no understanding of C++ classes such as `string`. Is there a reason that you're using `fscanf` over C++'s IO streams? – sepp2k Nov 27 '18 at 12:23
  • @sepp2k Well, one reason (maybe not a really good one) is that I understand how to properly format output using `printf` and the format specifiers, so it seemed kind of natural to use `scanf`. I know that there are formatting options using the C++ streams (like `setprecision` etc.), but I haven't understood them fully, yet, I think, because (so far) can't do some things using C++ streams that are straightforward to me using C functions.. – Lt. Frank Drebin Nov 27 '18 at 12:53
  • Mixing std::string with C style IO is dangerous and error prone. Don't do this. If you don't understand something about iostreams, ask a question about iostreams. – n. m. could be an AI Nov 27 '18 at 15:10

4 Answers4

0

As you could have read in the reference, it should be:

char str[<appropriate length here>];
fscanf(filepointer, "%s\n", str);

since str is an array. Read more in What is array to pointer decay?

There c_str() and data(), which can give you the C string of an std::string. However, thsese functions return a constant pointer, thus it cannot be used in fscanf() (which is a function coming from C, thus it handles C strings).

Note: In C++17, data() has an overload that returns a non-constant pointer, thus could be used for inline usage, like this:

std::string str(10, '\0');               // a string of size 10, null terminated
fscanf(filepointer, "%9s\n", str.data());
str.resize(strlen(str.data()));          // so that 'str.size()' is correct

Live Demo

But you should really be using C++ streams, like std::cin instead of C IO functions.

Example:

std::string str;
std::cin >> str;
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • The pointer returned by `c_str` is `const`, so you can't use it with `scanf`. – sepp2k Nov 27 '18 at 12:25
  • Of course, you're right.. I removed the `&` operator.. But that was not the point of the question and I know that I would get a C-String using `c_str()` or `data()` (as of c++11), but I want it the other way around... – Lt. Frank Drebin Nov 27 '18 at 12:29
  • 1
    Ok, so the edit came while I commented which renders my comment invalid now.. :D – Lt. Frank Drebin Nov 27 '18 at 12:37
  • 1
    @gsamaras yes, it does, thank you.. I will mark as answer.. comments and edits are arriving at a speed that is hard to keep up with :) – Lt. Frank Drebin Nov 27 '18 at 12:39
  • If you print `str.length()` in your live demo, you will be surprised. The `resize()` is needed if you want to use this object as an actuall `std::string`. – Kit. Nov 27 '18 at 12:54
  • Correct @Kit. updated, nice answers of yours too, which points that out, +1. Moreover, thank you for inspiring me with a [question](https://stackoverflow.com/questions/53500369/c-str-vs-data-when-it-comes-to-return-type)... – gsamaras Nov 27 '18 at 13:27
0

If you are using C++, you should not be using C or C-style functions. Especially functions like std::fscanf which does unprotected writes. Here is how to properly read data from a file to an std::string in C++: Read file line by line using ifstream in C++

op414
  • 582
  • 5
  • 15
  • *unprotected writes* – what is that? – Swordfish Nov 27 '18 at 12:32
  • An unprotected/unchecked write is when you write to a memory location without knowing if this location actually is part of the 'object' you _intend_ to write to. In your example if your `char str[]` array is smaller than the string in the file, `std::fscanf` will start writing outside of that array. If this happens to be in your program memory, you will get very wierd and unpredictable behaviour. If that's outside of your program memory you will get a segmentation fault. The fact that you ask about that shows that you have a lot to learn about memory management. – op414 Nov 27 '18 at 12:38
  • Sorry @Swordfish, I was referring to the the OP's code. Your example is still bad because you rely on writing the correct size of the array twice ('40' and '39') and because you modify the internal data of the `std::string` object without using a method. This breaks encapsulation, and can still lead to writing to wrong memory location (if you invalidate the pointer you get to the first character by mutating the string for example) – op414 Nov 27 '18 at 12:51
  • *[...] and can still lead to writing to wrong memory location (depending on how the string internally stores the data, which you can not make any assumptions on)* – Yes, I can: [basic.string/3](http://eel.is/c++draft/string.classes#basic.string-3) – Swordfish Nov 27 '18 at 13:02
  • Well pre-C++11 the `[]` operator is undefined when accessing index `size()`, which is what your code is doing when writing the null terminator if the input string happens to be 39 characters (null-terminator excluded) long. https://en.cppreference.com/w/cpp/string/basic_string/operator_at – op414 Nov 27 '18 at 13:34
  • I added a note regarding pre-C++11. Thanks. – Swordfish Nov 27 '18 at 13:42
0

One can use std::string with std::scanf() like that:

#include <cstdlib>   // EXIT_FAILURE
#include <cstdio>    // std::scanf()
#include <cstring>   // std::strlen()
#include <string>
#include <iostream>

int main()
{
    std::string foo(40, '\0');  // fill the string with 40 0s
                                // for pre C++11 please make it 41
    int length = 0;
    if (std::scanf("%39s%n", &foo[0], &length) != 1) {
        std::cerr << "Input error :(\n\n";
        return EXIT_FAILURE;
    }

    foo.resize(length);  // let foo know its own length ^^
    std::cout << foo.length() << " chars: " << foo << '\n';
}
Swordfish
  • 12,971
  • 3
  • 21
  • 43
0

Since C++17, you can:

std::string str(<appropriate length here>, 0);
fscanf(filepointer, "%s\n", str.data());
str.resize(strlen(str.c_str()));

But you shouldn't.

First of all, it is not doing what you want it to do. If you have an input line like "Tom Jerry\n", it will only read "Tom", and what is even worse, it will try to read "Jerry" next time you read from the file. You should really use fgets(), std::istream::getline(), or, better yet, std::getline(file, str).

Second, a '\0', or char() is a valid character in a C++ string. If you read "Tom\0Jerry\n", the actual string your code will manage to read will contain "Tom\0Jerry", which is probably a valid result, but then it will cut it to "Tom" because of that resize(strlen(str.c_str())). You can alleviate it with creative use of %n format specifier, but why complicate your life in the first place?

Third, C++ string input routines can resize the string when necessary. In C functions, you need to specify a string size limit, which is often arbitrary. That may lead to buffer overflows if you are sloppy with your format specifiers (like in your example), but also to logical errors if you don't handle the special cases of truncated input correctly.

So, just use std::getline(file, str) with C++ stream input, and you will avoid all this.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kit.
  • 2,386
  • 1
  • 12
  • 14
  • 1
    using `%s` without specifying a width is a no-no – Swordfish Nov 27 '18 at 12:53
  • *'\0' is a valid character in the std::string, but not a whitespace.* – woot? – Swordfish Nov 27 '18 at 13:03
  • What is your point? (cppreference.com and cplusplus.com are nice and all, but they are by no way normative) And whats your concern with whitespace? `scanf()` won't read any further, but that is expected. – Swordfish Nov 27 '18 at 13:10
  • [string.accessors](http://eel.is/c++draft/string.classes#string.accessors) says nothing like the note in your first link. – Swordfish Nov 27 '18 at 13:22
  • OK, clarified in the answer why one shouldn't do it at all. – Kit. Nov 27 '18 at 14:51