1
#include<iostream>
using namespace std;

int main() {

    string a, b, c;

    a = "World";
    b = "Apple";

    //As i've  not given any value to c still i'm able to use string c index 1 . HOW??
    //As we dont no that uptill how much index c string is formed

    c[1] = a[1];
    a[1] = b[1];
    b[1] = c[1];

    cout << a << endl;
    cout << b << endl;


    return 0;
}

Output

Wprld
Aople

I've learned that when form any string, and we assign its value, an array is formed containing the alphabets.

But when we don't assign any value to any string variable. What will be the default value of index in any string that will be assigned when we initiate any string variable ??

How we are able to access any string index as we don't know till how many indices that in that empty string exist??

  • You should NOT do that. `c` is not initialized yet. – digito_evo Dec 20 '21 at 16:42
  • 3
    `c[1] = a[1];` is out of bound access, leading to UB. – Jarod42 Dec 20 '21 at 16:43
  • You can know the `a.size()` which Returns the length of the string, in terms of bytes. – Mayur Dec 20 '21 at 16:54
  • 1
    In C++ errors are not often required to produce meaningful crashes or error messages. The program can often seem to ignore errors, allowing the error to corrupt the state of the program, leading to problems later on. Or the compiler is allowed to perform seemingly strange transformation when code would contain errors. The mistake is expecting an out of bounds access to cause something obviously wrong to happen immediately. This is often not the case in C++, when requiring an error would have a performance impact or otherwise be a burden to implementers. You just have to be vigilant. – François Andrieux Dec 20 '21 at 17:14
  • @digito_evo -- `c` has been initialized; that's what its constructor does. It doesn't have space for any characters, because that's what the default constructor does. – Pete Becker Dec 20 '21 at 17:19
  • "How we are able" [We are not](https://godbolt.org/z/qPWxeYa8d). – n. m. could be an AI Dec 20 '21 at 17:51

1 Answers1

1

First read about how std::string works. Check these links: An introduction to std::string and std::string

On my compiler (GCC v11.2) the size of small string buffer is 16. So a std::string object has a size of 8+8+16 == 32 bytes; A pointer to the buffer, an integer to store the capacity, and the SS buffer itself.

With all this in mind, now if you declare a std::string like this:

std::string str;

then the str will have a 16-byte buffer on the stack. But only the 0th index will be initialized to '\0' and the remaining 15 bytes might contain garbage. So if you print it out, it will only print the '\0' which will not be shown on the screen. So you'll see nothing.

Now take a look at this example:

#include <iostream>


int main( )
{
    std::string a; // default constructed
    std::string b; // default constructed
    std::string c( 10, '\0' ); // but c is now initialized with 10 null terminator
                               // characters and safe to be used with operator[]

    std::cout << "address of a: " << &a << '\n'
              << "address of b: " << &b << '\n'
              << "address of c: " << &c << '\n';

    std::cout << '\n';
    
    std::cout << std::showbase << std::hex
              << "address of a's underlying array: " << reinterpret_cast<uint64_t>( a.data( ) ) << '\n'
              << "address of b's underlying array: " << reinterpret_cast<uint64_t>( b.data( ) ) << '\n'
              << "address of c's underlying array: " << reinterpret_cast<uint64_t>( c.data( ) ) << '\n';

    std::cout << std::noshowbase << std::dec << '\n';

    std::cout << "size of a's underlying array: " << a.size( ) << '\n'
              << "size of b's underlying array: " << b.size( ) << '\n'
              << "size of c's underlying array: " << c.size( ) << '\n';

    std::cout << '\n';

    std::cout << "a's first element's ASCII: " << static_cast<int>( a[0] ) << '\n'
              << "b's first element's ASCII: " << static_cast<int>( b[0] ) << '\n'
              << "c's first element's ASCII: " << static_cast<int>( c[0] ) << '\n';

    std::cout << '\n';

    std::cout << "a's second element's ASCII: " << static_cast<int>( a[1] ) << '\n'
              << "b's second element's ASCII: " << static_cast<int>( b[1] ) << '\n'
              << "c's second element's ASCII: " << static_cast<int>( c[1] ) << '\n';

    std::cout << '\n';

    a = "World";
    b = "Apple";

    c[1] = a[1]; // here you modify a location that now contains actual data
    a[1] = b[1]; // ok
    b[1] = c[1]; // here you no longer read from a location that is uninitialized
                 // and thus does not contain garbage

    std::cout << a << '\n';
    std::cout << b << '\n';
    std::cout << c << '\n';
}

The possible output:

address of a: 0x5738bff710 //
address of b: 0x5738bff730 // these are the addresses of stack locations
address of c: 0x5738bff750 //

address of a's underlying array: 0x5738bff720 // these are also the addresses of stack
address of b's underlying array: 0x5738bff740 // locations since your strings are
address of c's underlying array: 0x5738bff760 // small and not heap allocated

size of a's underlying array: 0 // a has a 16 byte buffer but uninitialized
size of b's underlying array: 0 // b has a 16 byte buffer but uninitialized
size of c's underlying array: 10 // c has a 16 byte buffer but 10 chars are initialized

a's first element's ASCII: 0 // null terminator
b's first element's ASCII: 0 // null terminator
c's first element's ASCII: 0 // null terminator

a's second element's ASCII: 0 // null terminator but can be garbage too
b's second element's ASCII: 26 // here it's garbage
c's second element's ASCII: 0 // can only be null terminator since c's buffer
                              // has been initialized with 10 null terminators

Wprld
Aople
 o    // now you can see the letter `o` from the c variable being printed

When a std::string object is declared, a block of memory is allocated to it. If the number of chars that it stores is below a certain implementation defined limit (e.g. 16 or 24 byes), the block will be allocated on the stack. This trick is called as SSO. But if it's larger than that limit then a dynmaic memory allocation will need to happen to store the chars on the heap.

Now in your case, since you don't initialize c it means that it has an empty buffer on the stack. So your code can cause some undefined behavior if you use operator[] for indices greater than 0.

digito_evo
  • 3,216
  • 2
  • 14
  • 42