I have a code that looks as below:
typedef struct{
uint8_t distance[2];
uint8_t reflectivity;
}data_point;
typedef struct{
uint8_t flag[2];
uint8_t Azimuth[2];
std::vector<data_point> points; // always size is 32 but not initialized whereas filled in run-time using emplace_back
}data_block;
typedef struct{
uint8_t UDP[42];
std::vector<data_block> data; // always size is 12 but not initialized whereas filled in run-time using emplace_back
uint8_t time_stamp[4];
uint8_t factory[2];
}data_packet;
static std::vector<data_packet> packets_from_current_frame;
Assuming packets_from_current_frame.size() = 26, How can I calculate the number of bytes in packets_from_current_frame?
My solution on paper:
1 data_packet (assuming 32 points and 12 data) will have 42+ (12*(2+2+32(3))) + 4 + 2 = 1248. Hence, the end address is _begin + sizeof(uint8_t) * 26 * 1248 (_begin is the start address of the memory buffer).
With this calculation I always loose some data. Number of bytes that is lost depends on packets_from_current_frame.size(). What is wrong with the calculation?
Take a look at this code:
int main() {
static std::vector<data_packet> packets_from_current_frame;
std::cout << "sizeof data_point = " << (sizeof(data_point)) << std::endl;
std::cout << "sizeof data_block = " << (sizeof(data_block)) << std::endl;
std::cout << "sizeof data_packet = " << (sizeof(data_packet)) << std::endl;
return 0;
}
Output:
sizeof data_point = 3
sizeof data_block = 32
sizeof data_packet = 80
From this it is clear why your calculation fails.
You have forgotten to take into account things like the vector itself, fields after the vector and padding.
The correct way to calculate it is:
packets_from_current_frame.size() * (sizeof(data_packet) + data.size() * (sizeof(data_block) + points.size() * sizeof(data_point)))
Note: This amount of bytes are not stored in one consecutive memory block so don't try any direct memcpy.
The individual vector has consecutive data. If you want to know the address of the data held by a vector, use vector::data()
Related
While debugging my code I have noticed that something strange is going on, So I have added more lines and got confused even more:
#include <iostream>
#include <memory>
struct Node
{
size_t size = 0;
};
class MallocMetadata {
public:
size_t size; /** Effective allocation - requested size **/
bool is_free;
MallocMetadata *next;
MallocMetadata *prev;
MallocMetadata *next_free;
MallocMetadata *prev_free;
};
int main()
{
size_t size = 0;
auto node = std::make_shared<Node>();
int tmp_res=node->size - size - sizeof(MallocMetadata);
bool test=(node->size - size - sizeof(MallocMetadata)) < 128;
bool test1=tmp_res<128;
std::cout << tmp_res << "\n";
std::cout << test << "\n";
std::cout << test1 << "\n";
}
After running these 3 lines I saw:
tmp_res=-48
test = false
test1 = true
How is this even possible! why test is false, -48 is smaller than 128
Here's a proof:
It looks like the part node->size - size - sizeof(MallocMetadata) is calculated in unsigned integer.
When calculation of unsigned integer is going to be negative, the maximum number of the type plus one is added and the result wraparounds.
Therefore, the value looks like being big value (128 or more), making the expression (node->size - size - sizeof(MallocMetadata)) < 128 false.
In the other hands, int tmp_res=node->size - size - sizeof(MallocMetadata); will convert the big value to int. int is signed and it may give different value than the expression above that doesn't perform convertion to int.
I believe whenever there is an unsigned value in an expression, the result tends to be unsigned aswell.
size_t size = 0;
auto val = 1 + 100 + (-100) + size;
std::cout << typeid(val).name();
'val' will be a size_t aswell. So in your case, you're trying to store a negative value in size_t which causes overflow.
You can explicitly typecast it to a signed integer and that should be enough if I'm not mistaken.Like so:
bool test=int(node->size - size - sizeof(MallocMetadata)) < 128;
I am a c++ novice
After I calculated the pointer of the Player structure, the result was beyond my surprise
struct Player
{
const char* Name = "ab";
uintptr_t Health = 6;
uintptr_t Coins = 3;
} player;
std::cout << &player << std::endl; // 0100C000
uintptr_t* playerBaseAddress = (uintptr_t*)&player;
std::cout << playerBaseAddress << std::endl; // 0100C000
std::cout << (playerBaseAddress + 4) << std::endl; // 0100C010
0100C000+4 How do I get 0100C004 instead of 0100C010
Can someone explain that, please?
Like this
uintptr_t playerBaseAddress = (uintptr_t)&player;
In your version you have a pointer, so when you added 4 to your pointer the result was multiplied by the size of the object being pointed at. Clearly on your platform uintptr_t has size 4, so you got 0100C000 + 4*4 which is 0100C010.
This would also work
char* playerBaseAddress = (char*)&player;
because here the size of char is 1. so you get 0100C000 + 1*4 which equals 0100C004.
In pointer arithmetics, the sizes of the operations are multiplied by the pointed type's size.
This way it's easy to reference data right next to each other in memory.
For example:
int* ptr = new int[5];
ptr[3] = 4;
std::cout << *(ptr+3) << endl; // 4
delete[] ptr;
You could add four bytes to it by converting it to a pointer type which has the size of one byte, for example char*.
playerBaseAddress is of type uintptr_t* which is a pointer. Presumably uintptr_t takes 4 bytes in your environment. Now this piece
playerBaseAddress + 4
involves the pointer arithmetic: you move the pointer 4*sizeof(uintptr_t)=4*4=16 bytes forward. 16 in hex is 10. Hence your result.
Note that uintptr_t* playerBaseAddress = (uintptr_t*)&player; is UB anyway. I assume you meant uintptr_t playerBaseAddress = (uintptr_t)&player; instead.
Calculating the offset into a struct can be done by using offsetof, e.g.
#include <cstddef>
#include <iostream>
struct Player {
const char *name = "ab";
uintptr_t health = 6;
uintptr_t coins = 3;
};
int main()
{
size_t off = offsetof(Player, health);
std::cout << "off=" << off << '\n';
}
Will show 4 or 8, depending on the architecture and size of the structure's elements.
I came across this syntax for reading a BMP file in C++
#include <fstream>
int main() {
std::ifstream in('filename.bmp', std::ifstream::binary);
in.seekg(0, in.end);
size = in.tellg();
in.seekg(0);
unsigned char * data = new unsigned char[size];
in.read((unsigned char *)data, size);
int width = *(int*)&data[18];
// omitted remainder for minimal example
}
and I don't understand what the line
int width = *(int*)&data[18];
is actually doing. Why doesn't a simple cast from unsigned char * to int, int width = (int)data[18];, work?
Note
As #user4581301 indicated in the comments, this depends on the implementation and will fail in many instances. And as #NathanOliver- Reinstate Monica and #ChrisMM pointed out this is Undefined Behavior and the result is not guaranteed.
According to the bitmap header format, the width of the bitmap in pixels is stored as a signed 32-bit integer beginning at byte offset 18. The syntax
int width = *(int*)&data[18];
reads bytes 19 through 22, inclusive (assuming a 32-bit int) and interprets the result as an integer.
How?
&data[18] gets the address of the unsigned char at index 18
(int*) casts the address from unsigned char* to int* to avoid loss of precision on 64 bit architectures
*(int*) dereferences the address to get the referred int value
So basically, it takes the address of data[18] and reads the bytes at that address as if they were an integer.
Why doesn't a simple cast to `int` work?
sizeof(data[18]) is 1, because unsigned char is one byte (0-255) but sizeof(&data[18]) is 4 if the system is 32-bit and 8 if it is 64-bit, this can be larger (or even smaller for 16-bit systems) but with the exception of 16-bit systems it should be at minimum 4 bytes. Obviously reading more than 4 bytes is not desired in this case, and the cast to (int*) and subsequent dereference to int yields 4 bytes, and indeed the 4 bytes between offsets 18 and 21, inclusive. A simple cast from unsigned char to int will also yield 4 bytes, but only one byte of the information from data. This is illustrated by the following example:
#include <iostream>
#include <bitset>
int main() {
// Populate 18-21 with a recognizable pattern for demonstration
std::bitset<8> _bits(std::string("10011010"));
unsigned long bits = _bits.to_ulong();
for (int ii = 18; ii < 22; ii ++) {
data[ii] = static_cast<unsigned char>(bits);
}
std::cout << "data[18] -> 1 byte "
<< std::bitset<32>(data[18]) << std::endl;
std::cout << "*(unsigned short*)&data[18] -> 2 bytes "
<< std::bitset<32>(*(unsigned short*)&data[18]) << std::endl;
std::cout << "*(int*)&data[18] -> 4 bytes "
<< std::bitset<32>(*(int*)&data[18]) << std::endl;
}
data[18] -> 1 byte 00000000000000000000000010011010
*(unsigned short*)&data[18] -> 2 bytes 00000000000000001001101010011010
*(int*)&data[18] -> 4 bytes 10011010100110101001101010011010
I was playing around with memcpy when I stumbled on a strange result, where a memcpy that is called on the same pointer of memory after bool memcpy gives unexpected result.
I created a simple test struct that has a bunch of different type variables. I cast the struct into unsigned char pointer and then using memcpy I copy data from that pointer into separate variables. I tried playing around the offset of memcpy and shifting the int memcpy before bool (changed the layout of test struct so that the int would go before the bool too). Suprisingly the shifting fixed the problem.
// Simple struct containing 3 floats
struct vector
{
float x;
float y;
float z;
};
// My test struct
struct test2
{
float a;
vector b;
bool c;
int d;
};
int main()
{
// I create my structure on the heap here and assign values
test2* test2ptr = new test2();
test2ptr->a = 50;
test2ptr->b.x = 100;
test2ptr->b.y = 101;
test2ptr->b.z = 102;
test2ptr->c = true;
test2ptr->d = 5;
// Then turn the struct into an array of single bytes
unsigned char* data = (unsigned char*)test2ptr;
// Variable for keeping track of the offset
unsigned int offset = 0;
// Variables that I want the memory copied into they
float a;
vector b;
bool c;
int d;
// I copy the memory here in the same order as it is defined in the struct
std::memcpy(&a, data, sizeof(float));
// Add the copied data size in bytes to the offset
offset += sizeof(float);
std::memcpy(&b, data + offset, sizeof(vector));
offset += sizeof(vector);
std::memcpy(&c, data + offset, sizeof(bool));
offset += sizeof(bool);
// It all works until here the results are the same as the ones I assigned
// however the int value becomes 83886080 instead of 5
// moving this above the bool memcpy (and moving the variable in the struct too) fixes the problem
std::memcpy(&d, data + offset, sizeof(int));
offset += sizeof(int);
return 0;
}
So I expected the value of d to be 5 however it becomes 83886080 which I presume is just random uninitialized memory.
You ignore the padding of your data in a struct.
Take a look on the following simplified example:
struct X
{
bool b;
int i;
};
int main()
{
X x;
std::cout << "Address of b " << (void*)(&x.b) << std::endl;
std::cout << "Address of i " << (void*)(&x.i) << std::endl;
}
This results on my PC with:
Address of b 0x7ffce023f548
Address of i 0x7ffce023f54c
As you see, the bool value in the struct takes 4 bytes here even it uses less for its content. The compiler must add padding bytes to the struct to make it possible the cpu can access the data directly. If you have the data arranged linear as written in your code, the compiler have to generate assembly instructions on all access to align the data later which slows down your program a lot.
You can force the compiler to do that by adding pragma pack or something similar with your compiler. All the pragma things are compiler specific!
For your program, you have to use the address if the data for the memcpy and not the size of the data element before the element you want to access as this ignore padding bytes.
If I add a pragma pack(1) before my program, the output is:
Address of b 0x7ffd16c79cfb
Address of i 0x7ffd16c79cfc
As you can see, there are no longer padding bytes between the bool and the int. But the code which will access i later will be very large and slow! So avoid use of #pragma pack at all!
You've got the answer you need so I'll not get into detail. I just made an extraction function with logging to make it easier to follow what's happening.
#include <cstring>
#include <iostream>
#include <memory>
// Simple struct containing 3 floats
struct vector {
float x;
float y;
float z;
};
// My test struct
struct test2 {
float a;
vector b;
bool c;
int d;
};
template<typename T>
void extract(T& dest, unsigned char* data, size_t& offset) {
std::uintptr_t dp = reinterpret_cast<std::uintptr_t>(data + offset);
size_t align_overstep = dp % alignof(T);
std::cout << "sizeof " << sizeof(T) << " alignof " << alignof(T) << " data "
<< dp << " mod " << align_overstep << "\n";
if(align_overstep) {
size_t missing = alignof(T) - align_overstep;
std::cout << "misaligned - adding " << missing << " to align it again\n";
offset += missing;
}
std::memcpy(&dest, data + offset, sizeof(dest));
offset += sizeof(dest);
}
int main() {
std::cout << std::boolalpha;
// I create my structure on the heap here and assign values
test2* test2ptr = new test2();
test2ptr->a = 50;
test2ptr->b.x = 100;
test2ptr->b.y = 101;
test2ptr->b.z = 102;
test2ptr->c = true;
test2ptr->d = 5;
// Then turn the struct into an array of single bytes
unsigned char* data = reinterpret_cast<unsigned char*>(test2ptr);
// Variable for keeping track of the offset
size_t offset = 0;
// Variables that I want the memory copied into they
float a;
vector b;
bool c;
int d;
// I copy the memory here in the same order as it is defined in the struct
extract(a, data, offset);
std::cout << "a " << a << "\n";
extract(b, data, offset);
std::cout << "b.x " << b.x << "\n";
std::cout << "b.y " << b.y << "\n";
std::cout << "b.z " << b.z << "\n";
extract(c, data, offset);
std::cout << "c " << c << "\n";
extract(d, data, offset);
std::cout << "d " << d << "\n";
std::cout << offset << "\n";
delete test2ptr;
}
Possible output
sizeof 4 alignof 4 data 12840560 mod 0
a 50
sizeof 12 alignof 4 data 12840564 mod 0
b.x 100
b.y 101
b.z 102
sizeof 1 alignof 1 data 12840576 mod 0
c true
sizeof 4 alignof 4 data 12840577 mod 1
misaligned - adding 3 to align it again
d 5
24
There are apparently three padding bytes between the bool and the subsequent int. This is allowed by the standard due to alignment considerations (accessing a 4 byte int that is not aligned on a 4 byte boundary may be slow or crash on some systems).
So when you do offset += sizeof(bool), you are not incrementing enough. The int follows 4 bytes after, not 1. The result is that the 5 is not the first byte you read but the last one - you are reading three padding bytes plus the first one from test2ptr->d into d. And it is no coincidence that 83886080 = 2^24 * 5 (the padding bytes were apparently all zeros).
i'm trying to read from a binary file to a char array. When printing array entries, an arbitrary number (newline) and the desired number are being printed. I really cant get my head around this.
The first few bytes of the file are:
00 00 08 03 00 00 EA 60 00 00 00 1C 00 00 00 1C 00 00
My Code:
void MNISTreader::loadImagesAndLabelsToMemory(std::string imagesPath,
std::string labelsPath) {
std::ifstream is(imagesPath.c_str());
char *data = new char[12];
is.read(data, 12);
std::cout << std::hex << (int)data[2] << std::endl;
delete [] data;
is.close();
}
E.g it prints:
ffffff9b
8
8 is correct. The preceding number changes from execution to execution. And where does this newline come from?
You asked about reading data from a binary file and saving it into a char[] and you showed us this code that you submitted for your question:
void MNISTreader::loadImagesAndLabelsToMemory(std::string imagesPath,
std::string labelsPath) {
std::ifstream is(imagesPath.c_str());
char *data = new char[12];
is.read(data, 12);
std::cout << std::hex << (int)data[2] << std::endl;
delete [] data;
is.close();
}
And you wanted to know:
The preceding number changes from execution to execution. And where does this newline come from?
Before you can actually answer that question you need to know the binary file. That is what is the structure of the file internally. When you are reading data from a binary you have to remember that some program had written data to that file and that data was written in a structured format. It is this format that is unique to each family or file type of binary that is important. Most binaries will usually follow a common pattern such that they would container a header then maybe even sub headers then either clusters, or packets or chunks, etc. or even raw data after the header while some binaries may just be purely raw data. You have to know how the file is structured in memory.
What is the structure of the data?
Is the data type for the first entry into the file a char = 1 byte, int = 4 bytes (32bit system) 8 bytes (64bit system), float = 4bytes, double = 8bytes, etc.
According to your code you have an array of char with a size of 12 and knowing that a char is 1 byte in memory you are asking for 12 bytes. Now the problem here is that you are pulling off 12 consecutive individual bytes in a row and by not knowing the file structure how can you determine if the first byte was an actual char written or an unsigned char, or a int?
Consider these two different binary file structures that are created by C++ structs that contains all the needed data and both are written out to a file in a binary format.
A Generic Header Structure that both File Structures will use.
struct Header {
// Size of Header
std::string filepath;
std::string filename;
unsigned int pathSize;
unsigned int filenameSize;
unsigned int headerSize;
unsigned int dataSizeInBytes;
};
FileA Unique Structure For File A
struct DataA {
float width;
float length;
float height;
float dummy;
}
FileB Unique Structure For File B
struct DataB {
double length;
double width;
}
A File in memory in general would be something like this:
First Few Bytes are the path and file name and stored sizes
This can vary from file to file depending on how many characters
are used for both the file path and file name.
After the strings we do know that the next 4 data types are unsigned
so we know that on a 32bit system it will be 4bytes x 4 = 16 total bytes
For a 64bit system it will be 8bytes x 4 = 32 total bytes.
If we know the system architecture then we can get past this easily enough.
Of these 4 unsigned(s) the first two are for the length of the path and filename. Now these could be the first two read in from the file and not the actual paths. The order of these could be reversed.
It is the next 2 unsigned(s) that are of importance
The next is the full size of the header and can be used to read in and skip over the header.
The next one tells you the size of the data to be pulled in now these could be in chunks with a count of how many chunks because it could be a series of the same data structures but for simplicity I left out chunks and counts and using a single instance structure.
It is here were we can then extract the amount of data in bytes by how many bytes to extract.
Lets consider the two different binary files where we are already past all the header information and we are reading in the bytes to parse. We get to the size of the data in bytes and for FileA we have 4 floats = 16bytes and for FileB we have 2 doubles = 16bytes. So now we know how to call the method to read in x amount of data for a y type of data. Since y is now a type and x is amount of we can say this: y(x) As if y is a built in type and x is a numerical initializer for the default built in type of constructor for this built in type either it be an int, float, double, char, etc.
Now let's say we were reading in either one of these two files but didn't know the data structure and how its information was previously stored to the file and we are seeing by the header that the data size is 16 bytes in memory but we didn't know if it was being stored as either 4 floats = 16 bytes or 2 doubles = 16 bytes. Both structures are 16 bytes but have a different amount of different data types.
The summation of this is that without knowing the file's data structure and knowing how to parse the binary does become an X/Y Problem
Now let's assume that you do know the file structure to try and answer your question from above you can try this little program and to check out some results:
#include <string>
#include <iostream>
int main() {
// Using Two Strings
std::string imagesPath("ImagesPath\\");
std::string labelsPath("LabelsPath\\");
// Concat of Two Strings
std::string full = imagesPath + labelsPath;
// Display Of Both
std::cout << full << std::endl;
// Data Type Pointers
char* cData = nullptr;
cData = new char[12];
unsigned char* ucData = nullptr;
ucData = new unsigned char[12];
// Loop To Set Both Pointers To The String
unsigned n = 0;
for (; n < 12; ++n) {
cData[n] = full.at(n);
ucData[n] = full.at(n);
}
// Display Of Both Strings By Character and Unsigned Character
n = 0;
for (; n < 12; ++n) {
std::cout << cData[n];
}
std::cout << std::endl;
n = 0;
for (; n < 12; ++n) {
std::cout << ucData[n];
}
std::cout << std::endl;
// Both Yeilds Same Result
// Okay lets clear out the memory of these pointers and then reuse them.
delete[] cData;
delete[] ucData;
cData = nullptr;
ucData = nullptr;
// Create Two Data Structurs 1 For Each Different File
struct A {
float length;
float width;
float height;
float padding;
};
struct B {
double length;
double width;
};
// Constants For Our Data Structure Sizes
const unsigned sizeOfA = sizeof(A);
const unsigned sizeOfB = sizeof(B);
// Create And Populate An Instance Of Each
A a;
a.length = 3.0f;
a.width = 3.0f;
a.height = 3.0f;
a.padding = 0.0f;
B b;
b.length = 5.0;
b.width = 5.0;
// Lets First Use The `Char[]` Method for each struct and print them
// but we need 16 bytes instead of `12` from your problem
char *aData = nullptr; // FileA
char *bData = nullptr; // FileB
aData = new char[16];
bData = new char[16];
// Since A has 4 floats we know that each float is 4 and 16 / 4 = 4
aData[0] = a.length;
aData[4] = a.width;
aData[8] = a.height;
aData[12] = a.padding;
// Print Out Result but by individual bytes without casting for A
// Don't worry about the compiler warnings and build and run with the
// warning and compare the differences in what is shown on the screen
// between A & B.
n = 0;
for (; n < 16; ++n) {
std::cout << aData[n] << " ";
}
std::cout << std::endl;
// Since B has 2 doubles weknow that each double is 8 and 16 / 8 = 2
bData[0] = b.length;
bData[8] = b.width;
// Print out Result but by individual bytes without casting for B
n = 0;
for (; n < 16; ++n) {
std::cout << bData[n] << " ";
}
std::cout << std::endl;
// Let's Print Out Both Again But By Casting To Their Approriate Types
n = 0;
for (; n < 4; ++n) {
std::cout << reinterpret_cast<float*>(aData[n]) << " ";
}
std::cout << std::endl;
n = 0;
for (; n < 2; ++n) {
std::cout << reinterpret_cast<double*>(bData[n]) << " ";
}
std::cout << std::endl;
// Clean Up Memory
delete[] aData;
delete[] bData;
aData = nullptr;
bData = nullptr;
// Even By Knowing The Appropriate Sizes We Can See A Difference
// In The Stored Data Types. We Can Now Do The Same As Above
// But With Unsigned Char & See If It Makes A Difference.
unsigned char *ucAData = nullptr;
unsigned char *ucBData = nullptr;
ucAData = new unsigned char[16];
ucBData = new unsigned char[16];
// Since A has 4 floats we know that each float is 4 and 16 / 4 = 4
ucAData[0] = a.length;
ucAData[4] = a.width;
ucAData[8] = a.height;
ucAData[12] = a.padding;
// Print Out Result but by individual bytes without casting for A
// Don't worry about the compiler warnings and build and run with the
// warning and compare the differences in what is shown on the screen
// between A & B.
n = 0;
for (; n < 16; ++n) {
std::cout << ucAData[n] << " ";
}
std::cout << std::endl;
// Since B has 2 doubles weknow that each double is 8 and 16 / 8 = 2
ucBData[0] = b.length;
ucBData[8] = b.width;
// Print out Result but by individual bytes without casting for B
n = 0;
for (; n < 16; ++n) {
std::cout << ucBData[n] << " ";
}
std::cout << std::endl;
// Let's Print Out Both Again But By Casting To Their Approriate Types
n = 0;
for (; n < 4; ++n) {
std::cout << reinterpret_cast<float*>(ucAData[n]) << " ";
}
std::cout << std::endl;
n = 0;
for (; n < 2; ++n) {
std::cout << reinterpret_cast<double*>(ucBData[n]) << " ";
}
std::cout << std::endl;
// Clean Up Memory
delete[] ucAData;
delete[] ucBData;
ucAData = nullptr;
ucBData = nullptr;
// So Even Changing From `char` to an `unsigned char` doesn't help here even
// with reinterpret casting. Because These 2 Files Are Different From One Another.
// They have a unique signature. Now a family of files where a specific application
// saves its data to a binary will all follow the same structure. Without knowing
// the structure of the binary file and knowing how much data to pull in and the big key
// word here is `what type` of data you are reading in and by how much. This becomes an (X/Y) Problem.
// This is the hard part about parsing binaries, you need to know the file structure.
char c = ' ';
std::cin.get(c);
return 0;
}
After running the short program above don't worry about what each value being displayed to the screen is; just look at the patterns that are there for the comparison of the two different file structures. This is just to show that a struct of floats that is 16 bytes wide is not the same as a struct of doubles that is also 16 bytes wide. So when we go back to your problem and you are reading in 12 individual consecutive bytes the question then becomes what does these first 12 bytes represent? Is it 3 ints or 3 unsigned ints if on 32bit machine or 2 ints or 2 unsigned ints on a 64bit machine, or 3 floats, or is a combination such as 2 doubles and 1 float? What is the current data structure of the binary file you are reading in?
Edit In my little program that I wrote; I did forget to try or add in the << std::hex << in the print out statements they can be added in as well were each printing of the index pointers are used but there is no need to do so because the output to the display is the same exact thing as this only shows or expresses visually the difference of the two data structures in memory and what their patterns look like.