Печать адресов вектора <char> элементы показывают мусор

Рассматривать:

#include <vector>
#include <string>
#include <iostream>
using namespace std;

int main()
{
    vector<char> vChar;
    vChar.push_back('a');
    vChar.push_back('b');
    vChar.push_back('c');
    vChar.push_back('d');

    vector<int> vInt;
    vInt.push_back(1);
    vInt.push_back(2);
    vInt.push_back(3);
    vInt.push_back(4);

    cout << "For char vector Size:" << vChar.size() << " Capacity:" << vChar.capacity() << "\n";
    for(int i=0; i < vChar.size(); i++)
    {
        cout << "Data: " << vChar[i] << " Address:" <<  &vChar[i]  << "\n";
    }

    cout << "\nFor int vector Size:" << vInt.size() << " Capacity:" << vInt.capacity() << "\n";
    for (int i = 0; i < vInt.size(); i++)
    {
        cout << "Data: " << vInt[i] << " Address:" << &vInt[i] << "\n";
    }

    return 0;
}

Пример вывода для приведенного выше кода:

For char vector Size:4 Capacity:4
Data: a Address:abcd²²²²αPⁿ▀┬
Data: b Address:bcd²²²²αPⁿ▀┬
Data: c Address:cd²²²²αPⁿ▀┬
Data: d Address:d²²²²αPⁿ▀┬

For int vector Size:4 Capacity:4
Data: 1 Address:000001F020F80420
Data: 2 Address:000001F020F80424
Data: 3 Address:000001F020F80428
Data: 4 Address:000001F020F8042C

Для каждого примитивного типа данных ячейки памяти смежны, за исключением char. Он выводит на экран некоторую величину мусора.

Я попытался добавить v.reserve(4), но результат был тот же.

Ответ 1

Для каждого примитивного типа данных ячейки памяти являются смежными, кроме char. Он выводит некоторые значения мусора на экран.

"Области памяти" являются смежными одинаково для обоих случаев. Разница лишь в том, как вы отображаете свои результаты. Когда вы делаете:

cout << "Data: " << vChar[i] << " Address:" <<  &vChar[i]  << "\n";

вы даете std::operator<<(std::basic_ostream) char*, так как вы применяете & (address-of) к одному char 1 из vector, что заставляет его рассматривать его как стиль C строка - то есть ищет завершающий ноль В вашем случае этот нуль действительно после некоторого мусора действительно. 2 Но у вас обязательно будет какой-то мусор после vector<int>, только вы его не печатаете. 3

Если вы хотите получить ту же распечатку, что и для vector<int>, то вы можете явно привести к void указателю, поэтому std::cout будет рассматривать его как адрес для печати (здесь перегрузка (7)), а не строка:

cout << "Data: " << vChar[i] << " Address:" <<  static_cast<void*>(&vChar[i])  << "\n";

В этом случае вывод:

For char vector Size:4 Capacity:4
Data: a Address:0x1c39810
Data: b Address:0x1c39811
Data: c Address:0x1c39812
Data: d Address:0x1c39813

For int vector Size:4 Capacity:4
Data: 1 Address:0x1c39960
Data: 2 Address:0x1c39964
Data: 3 Address:0x1c39968
Data: 4 Address:0x1c3996c

1char& если быть точным, так как std::vector<T>::operator[] возвращает T&.

2 Обратите внимание, что поиск этого завершающего нуля, который не был помещен вами, представляет собой неопределенное поведение, так как он потенциально дает вам доступ к памяти, к которой этот доступ не предназначен.

3 Вы можете сами убедиться в этом, если выполните обратное приведение, чтобы std::cout рассматривал vector<int> элементы vector<int> как строки в стиле C:

cout << "Data: " << vInt[i] << " Address:" << reinterpret_cast<char*>(&vInt[i]) << "\n";

Опять же, просто запомните, что это означает неопределенное поведение, так как код печати будет искать в памяти завершающий ноль, в то время как у вас точно не было его для его поиска.

Ответ 2

std::vector<T>::operator[]() возвращает T&, адрес которого в случае char будет отформатирован с помощью перегрузки (2) operator<<(std::basic_ostream) как если бы это было завершение с нулевой operator<<(std::basic_ostream) C -style string (это строка, которая начинается с &vChar[i] и останавливается при первом найденном \0).

Чтобы заставить его работать, используйте std::cout << static_cast<const void*>(&vChar[i]) чтобы получить перегрузку (7) std::basic_ostream::operator<<().