I have been working on a programming assignment about operating system. Your videos help me extremely well, I was thinking like why your videos always pop up when I am searching the keyword on RUclips. You literally taught everything I need in these few days like fork() exec() wait4() dup2(), the int argc, char*argv[] and now the dynamic allocation of multi-dimensional array. Although I have take a course in c++, operating system with C language still a big barrier for me. I can't be more thankful toward your videos. Big Thank you to you
Thank you so much for this clear explanation. I always wondered why a 3 x 3 int ** matrix took up more than just 4 * 3 * 3 = 36 bytes of memory. Now I know the extra memory is for the int ** taking up 8 bytes and each row consisting of an int * taking up 8 bytes. I've been mostly using garbage collected languages like Python and Java, but I'm motivated to learn C so that I can contribute to open source projects in the future.
Assuming that the compiler is not smart enough to figure out something more efficient, this allocation scheme is really bad. Typically the smallest chunk allocated from malloc is 32-bytes long. Thus this code consumes 8*3=24 rounded to 32 bytes for pointers and 3*32 bytes for the data itself. It gives 128 bytes rather than optimal 36.
I love this video, and what it taught me. I was lost when it came to why my memory allocated 2D char array for a problem I was doing was not working. This man has saved me!
Hey, thanks so much for the video. I currently have a university class for c programming, but my lecturer didn't upload a video concerning dynamically allocated multi-dim arrays, so I had to visit youtube. Found your channel, very good explanation!
This ties in well with why sizeof works differently for arrays.. the compiler/program is aware of the size and type, it's not in user memory. With the dynamic allocation we have to have it record it in user memory. a 1d array would be space efficient but would require more calculations to index so it's a classic memory vs performance issue.
hey man! I don't really know what K&R looks like, but if it's called the C Bible, I think this would be the video version of it. NIce nice tutorials. I'll probably watch them all XD
Or simple use a pointer to an array. `int (*arr)[3] = calloc(3, sizeof *arr);` and it's done. Free with `free(arr)`. Works perfectly even if dimensions are not constants. try: `int (*arr)[m] = calloc(n, sizeof *arr);` to dynamically allocate n-by-m array
This also has the benefit of ensuring that all of the array memory will be contiguous as its being allocated all at once. Using a loop to allocate each element of the ** might not get contiguous memory.
You can simply have a number that increments for every integer you read. Once you hit a new line, you know that's the number of elements in a column. For rows, you'd have to read each line in the file to find out (basically the same idea) You could use realloc() if you need more space in an array you've allocated dynamically
i understand why you did set **arr to NULL ( because it is in the stack and when the line "free(arr)"execute the memory in the heap will get free and **arr still exist because it is in the stack which mean it is pointing somewhere ) , right? what i can not understand why you did set each one of arr[i] "in the for loop "to NULL each one of them is in the heap which mean once the line "free(arr)"execute. each one of them will be deleted?
You're completely right. It's just good practice. After freeing memory you should always set that pointer to that memory to NULL, that way it's MUCH easier to debug and fix your code. Basically, if you accidentally dereference that pointer that you just freed the memory of... there are two possibilities: 1) Pointer is NULL so the program crashes gracefully, thus you can see where your issue is 2) Your program accesses memory that (by chance) has access to and continues executing until it breaks elsewhere (this is much harder to track down)
Thanks bro, do more such unexplained but important topic videos. Btw how to allocate more size for specific sub array inside main array? For example I want to allocate 8 int space for a[1] and a[2] , and for rest 5 int spaces?
It's gonna have to be dynamically allocated. So something like this: int** arr = malloc(sizeof(int*) * 3); for (int i = 0; i < 3; i++) { if (i == 1 || i == 2) { arr[i] = malloc(sizeof(int) * 8); } else { arr[i] = malloc(sizeof(int) * 5); } }
11:55 Hi. Please explain the formula. I understand the sizeof(arr) (just numbers of 2d array) +size (arr[0]*3) (the weigh of one pointer multiplied by 3) but what is sizeof(int) 3*3 for?
sizeof(arr) - the size of the double pointer to the pointer array. Should be 8 bytes sizeof(arr[0]) * 3 - the size of the pointers to the arrays. Should be 8 * 3 = 24 bytes sizeof(int) * 3 * 3 - the size of all integers of the whole array. Should be 4 * 3 * 3 = 36 bytes ^ this is the same as sizeof(arr[0][0]) * 3 * 3 Total is 68 bytes
Hello, when so i made the 2d dynamic array same as you but i keep getting compile warning "assignment to ‘int’ from ‘void *’ makes integer from pointer without a cast **regTable = malloc(sizeof(int*) * (numOfStudents*numOfCourses)); ^ " Im just confused why it worked in the video and not for me, i tried type casting as int and int* and still gives warning, the variable is first declared above main as "int **regTable;" any help with this would be greatly appreciated
Would this method also be appropriate to use when you don't know how many dynamically allocated 1d arrays you need before hand or say you want to load in multiple bitmaps in memory and don't know how many or the max sized bitmap at compile time can you expand this to be a dynamically allocated 3d array? Or is there a better way to use pointers in those cases?
Hello! I wanted to know about how to free the dynamic array which is initialized in a function that has to return that array and then I want to free that initialized array.
Hi! Dynamic memory initialized in a function doesn't have to be freed in the same function. You can free it anywhere you want as long as you have a pointer to it
Does architecture depends on compiler ?? Since my laptop is based on 64bit architecture but though my result shows the value of sizeof(int**) as 4 only.
Yes (kinda). Your compiler can be configured to build the application for all sorts of architectures (x86, x64, ARM etc.). Modern computers can run both 32-bit and 64-bit applications. So you have your compiler configured to create a 32-bit executable and you're running that on your 64-bit machine in compatibility mode
can you automatically update the arrays with inputs of 4 bytes? without first storing the bytes then allocating them manually into each section of the array????
@@CodeVault Basically, i'm creating a RFID scanner that stores the UIDs every time a card is detected, i want it to store up to 10 attempts and each UID consists of 4 bytes i.e. 192 313 414 21, can i have it so whenever a card gets scanned it updates the same list with every new card being detected without overwriting previous scans
Well, there are 3 types of memory in C. Global, stack and heap. If you want the array to be allocated at all times you could allocate it globally. On the stack, it wouldn't work usually since the memory gets deallocated ones the call returns. The heap is sort of the middle ground, where you control when it gets allocated and deallocated so it's easier to optimize your memory usage.
@@CodeVault thanks a lot!!! but globally declared array does not require pointers which consume significant amount of memory(as we can see for heap),still when the question is about like..using a fuxed amount of memory again and again and a large amount of data,i believe heap has the upper hand there!! am i right?? TIA!! keep up the good work,btw!
@@CodeVault Actually the proper wording should be "static", "automatic" and "dynamic", respectively. It refers to the lifetime of objects. Static are persistent, automatic are freed when the identified goes out of scope (not only when function ends) and dynamic are freed on demand. Word "global" is not technically correct because one can have static variables defined inside the function, or even a nested scope that is not accessible from outside.
I have a problem I’ve been working on. The program sums up a value with a certain number of child processes. It asks the user for number of terms and child processes to split the calculation for the number of terms. I started with making a two dimensional array. How would I loop to make just four child processes and reference the pipe for the child? Example the user puts 1000 and 4 so 1000 terms and 4 child processes.
I'm not sure I understand your question. You have a 2D array and you want to know how you sum each subarray (say of 250 elements in that case) in each child process? Is that right?
@@CodeVault Yes, so i watched your video on pipes for two way communication which helped. I run into a problem when I try to use a for loop. I create the pipe based on the user input for child processes. I need to pass from the parent the binary values of the terms, number of child processes, and say j from the for loop. Then the child does it sums and passes to the next and when they're done they pass it back to the parent to get the total. So my output would be something like Child #1 approximation for sum over 0-250 Child #2 for sum 251-500... etc and the parent would print the total. The problem i have is how do I know which child process is what. I also have a hard time figuring out how to fork() just four children. I wrote something like this int(*fd)[2]; fd = (int(*)[])calloc(4,sizeof(fd[0]); for(int j = 0; j < 4; j++) pipe(fd[j]) if(fork() == 0)//Child { read(fd[j])??? } else { //Parent? }
To fork just 4 children you'd have to call fork() only in the parent process. Like this: int pids[4] = { 0 }; for (int i = 0; i < 4; i++) { pids[i] = fork(); if (pids[i] == 0) { break; } } So for every fork, each child process would hit the break statement and exit, while the parent would keep creating processes.
Next, to figure out which process is which you have to think about how the pids array is looking in each of the processes (suppose id1, id2, id3 and id4 are the child processes ids): parent pids[4] is { id1, id2, id3, id4 }; child1 pids[4] is { 0, 0, 0, 0 }; since it never assigned any other value than 0 child2 pids[4] is { id1, 0, 0, 0 }; child3 pids[4] is {id1, id2, 0, 0}; child4 pids[4] is {id1, id2, id3, 0}; So, to figure out which process is which, you can just check the first occurence of 0 in the pids array: for (int i = 0; i < 4; i++) { if (pids[i] == 0) { // ... Code executed only by child i break; // Break so that it only executes once per process } }
Even though you are on a 64 bit machine you might be compiling your program for 32 bit. Also, make sure you dont' confuse int types with int* types. The former has 4 bytes and the latter has 8 bytes (on 64 bit).
Whenever we want to get the address of something. For arrays we don't have to do this since it's redundant: int arr[5]; printf("Address of array is %p", &arr); // is the same as printf("Address of array is %p", arr); There's this video on the topic: code-vault.net/lesson/4rx6a7yjaz:1603820088963 and a continuation code-vault.net/lesson/wxugezii3r:1603820089003
Oh no! This is not optimal. You want to allocate all elements in one block. Then all elements are still continuous in memory. Then you can pass your 2D-array into BLAS algorithms etc. without further modifications. Also if you have many rows there will be many calls to malloc. Malloc is a system call and hence very slow. If you first allocate a array of pointers, for the beginning of each row, and then allocate the elements, you get away with two allocation no matter the dimensions of the 2D-array. Doing one allocation for each row will make you program slow.
Huh, in these videos I sometimes omit certain aspects such as performance or certain good practices that are used on production-ready projects (if they take away from the subject at hand). But this intriguing and I might consider making its own video. Just to fully understand: You mean to just allocate the pointers, allocate the block for all values (malloc(x * y * sizeof(int))) and then assign all the pointers to their respective addresses?
I have spent 3 days trying to figure this out for a school project. Your explanation was clear and to the point. Good Job! thank you!!!
You've demystified so many C concepts for me that I couldn't grasp through other explanations. Thank you so much!
I have been working on a programming assignment about operating system. Your videos help me extremely well, I was thinking like why your videos always pop up when I am searching the keyword on RUclips. You literally taught everything I need in these few days like fork() exec() wait4() dup2(), the int argc, char*argv[] and now the dynamic allocation of multi-dimensional array. Although I have take a course in c++, operating system with C language still a big barrier for me. I can't be more thankful toward your videos. Big Thank you to you
Thank you for your talk throughs they really helped me to understand multidimensional arrays that much more! Good job!
Thank you so much for this clear explanation. I always wondered why a 3 x 3 int ** matrix took up more than just 4 * 3 * 3 = 36 bytes of memory. Now I know the extra memory is for the int ** taking up 8 bytes and each row consisting of an int * taking up 8 bytes. I've been mostly using garbage collected languages like Python and Java, but I'm motivated to learn C so that I can contribute to open source projects in the future.
Assuming that the compiler is not smart enough to figure out something more efficient, this allocation scheme is really bad. Typically the smallest chunk allocated from malloc is 32-bytes long. Thus this code consumes 8*3=24 rounded to 32 bytes for pointers and 3*32 bytes for the data itself. It gives 128 bytes rather than optimal 36.
Read many articles , watched many videos but no one explained it like you did . Thank you.
thank you so much .
man im trying this for about 20 hours you are my hero !!
Probably the best programming explanation I've ever heard and seen. You get my like and sub.
In a few minutes you saved me from hours of debugging tryina find what's wrong with it, tysm
I love this video, and what it taught me. I was lost when it came to why my memory allocated 2D char array for a problem I was doing was not working. This man has saved me!
Hey, thanks so much for the video. I currently have a university class for c programming, but my lecturer didn't upload a video concerning dynamically allocated multi-dim arrays, so I had to visit youtube. Found your channel, very good explanation!
So happy i discovered your channel !! thanks for the explanation and continue the good work
Thank you, exactly what I needed! Liked and Subscribed.
This tutoral is great! Thank you. I needed to (or i think it was the best option) create a 3D array and could easily apply your method.
Awesome video. We really need a programmer like you in youtube.
Crystal clear explanation on how to manage dynamically allocated multi-dimensional arrays in C !
Thank you so much! Very nicely explained. Liked and Subscribed!
This ties in well with why sizeof works differently for arrays.. the compiler/program is aware of the size and type, it's not in user memory. With the dynamic allocation we have to have it record it in user memory. a 1d array would be space efficient but would require more calculations to index so it's a classic memory vs performance issue.
thanks, you answered one of my important question.
thank you for the easy to follow explanation!
Well done. Thank you!
bruh you just demystified so much
Great illustrations with the diagrams
THANK u i really understand with tables and draws the lessons
Thank you, it is very clear and helpful
hey man! I don't really know what K&R looks like, but if it's called the C Bible, I think this would be the video version of it. NIce nice tutorials. I'll probably watch them all XD
Finally i found someone how explain this, thank you teacher
Perfect explanation, zero dislikes!
just what I needed, thanks
Thank you sir.
awesome tutorial sir
Thanks GOD , i got this video..now, my concept is very clear...Thank you sir, you deserve 1 million subscriber...please make video on data structure
Thank you :)
thank you so much sir :)
very good presentation.
great video sir
Or simple use a pointer to an array. `int (*arr)[3] = calloc(3, sizeof *arr);` and it's done. Free with `free(arr)`. Works perfectly even if dimensions are not constants. try: `int (*arr)[m] = calloc(n, sizeof *arr);` to dynamically allocate n-by-m array
This also has the benefit of ensuring that all of the array memory will be contiguous as its being allocated all at once. Using a loop to allocate each element of the ** might not get contiguous memory.
Sir how to get the number of rows and columns in a matrix read from a file.Thank
You can simply have a number that increments for every integer you read. Once you hit a new line, you know that's the number of elements in a column. For rows, you'd have to read each line in the file to find out (basically the same idea)
You could use realloc() if you need more space in an array you've allocated dynamically
Thank you
Thank you very much :)
thank you so muchhh
Thank you !!!
i understand why you did set **arr to NULL ( because it is in the stack and when the line "free(arr)"execute the memory in the heap will get free and **arr still exist because it is in the stack which mean it is pointing somewhere ) , right?
what i can not understand why you did set each one of arr[i] "in the for loop "to NULL each one of them is in the heap which mean once the line "free(arr)"execute. each one of them will be deleted?
You're completely right. It's just good practice. After freeing memory you should always set that pointer to that memory to NULL, that way it's MUCH easier to debug and fix your code. Basically, if you accidentally dereference that pointer that you just freed the memory of... there are two possibilities:
1) Pointer is NULL so the program crashes gracefully, thus you can see where your issue is
2) Your program accesses memory that (by chance) has access to and continues executing until it breaks elsewhere (this is much harder to track down)
Thanks bro, do more such unexplained but important topic videos.
Btw how to allocate more size for specific sub array inside main array?
For example I want to allocate 8 int space for a[1] and a[2] , and for rest 5 int spaces?
It's gonna have to be dynamically allocated. So something like this:
int** arr = malloc(sizeof(int*) * 3);
for (int i = 0; i < 3; i++) {
if (i == 1 || i == 2) {
arr[i] = malloc(sizeof(int) * 8);
} else {
arr[i] = malloc(sizeof(int) * 5);
}
}
11:55 Hi. Please explain the formula. I understand the sizeof(arr) (just numbers of 2d array) +size (arr[0]*3) (the weigh of one pointer multiplied by 3) but what is sizeof(int) 3*3 for?
sizeof(arr) - the size of the double pointer to the pointer array. Should be 8 bytes
sizeof(arr[0]) * 3 - the size of the pointers to the arrays. Should be 8 * 3 = 24 bytes
sizeof(int) * 3 * 3 - the size of all integers of the whole array. Should be 4 * 3 * 3 = 36 bytes
^ this is the same as sizeof(arr[0][0]) * 3 * 3
Total is 68 bytes
@@CodeVault Thank you
GOLD
Thanks.
Amazing🙌🏾
Great video!!!!!!! No wonder you have 0 dislikes.
Hello, when so i made the 2d dynamic array same as you but i keep getting compile warning
"assignment to ‘int’ from ‘void *’ makes integer from pointer without a cast
**regTable = malloc(sizeof(int*) * (numOfStudents*numOfCourses));
^
"
Im just confused why it worked in the video and not for me, i tried type casting as int and int* and still gives warning, the variable is first
declared above main as "int **regTable;"
any help with this would be greatly appreciated
**regTable is an int
malloc returns a pointer.
I think you meant to do *regTable = malloc(...);
wao it working fine. Nice explaination : )
You're the best
Would this method also be appropriate to use when you don't know how many dynamically allocated 1d arrays you need before hand or say you want to load in multiple bitmaps in memory and don't know how many or the max sized bitmap at compile time can you expand this to be a dynamically allocated 3d array? Or is there a better way to use pointers in those cases?
Depending on what you need this could be a solution. Another one would be to flatten the array and calculate offsets based on the indices
Hello! I wanted to know about how to free the dynamic array which is initialized in a function that has to return that array and then I want to free that initialized array.
Hi! Dynamic memory initialized in a function doesn't have to be freed in the same function. You can free it anywhere you want as long as you have a pointer to it
Is it enough to only free the outer array and set it to zero without freeing each individual sub array in advance or will this cause a problem?
It will cause a memory leak. You basically have to have a free() call for each malloc/calloc call
@@CodeVault Thank you!
Does architecture depends on compiler ??
Since my laptop is based on 64bit architecture but though my result shows the value of sizeof(int**) as 4 only.
Yes (kinda). Your compiler can be configured to build the application for all sorts of architectures (x86, x64, ARM etc.). Modern computers can run both 32-bit and 64-bit applications. So you have your compiler configured to create a 32-bit executable and you're running that on your 64-bit machine in compatibility mode
Is it not required to typecast to int** or int* after dynamic allocation? Please reply
It's never required. Although it is recommended to do so in C++... in C it's not recommended
can you automatically update the arrays with inputs of 4 bytes? without first storing the bytes then allocating them manually into each section of the array????
I'm not sure what you mean. Can you give me an example please?
@@CodeVault Basically, i'm creating a RFID scanner that stores the UIDs every time a card is detected, i want it to store up to 10 attempts and each UID consists of 4 bytes i.e. 192 313 414 21, can i have it so whenever a card gets scanned it updates the same list with every new card being detected without overwriting previous scans
if you have discord i can show you properly what i mean pls Ai#0001
You can send the code to discord.code-vault.net
There are people there that can help you
Why do you use the format specifier "%llu" instead of "%d"?
Im new to C language!
Because sizeof() returns a "unsigned long long" type (64-bit unsigned integer) instead of an "int" type (32-bit signed integer)
@@CodeVault got it, thank you!
How to get output in external window in vs code ?
There's an option in launch.json: code.visualstudio.com/docs/cpp/launch-json-reference#_externalconsole
also, why do we need to allocate an array dynamically?
can't do we anything else to replace the dynamically allocated array?
Well, there are 3 types of memory in C. Global, stack and heap. If you want the array to be allocated at all times you could allocate it globally. On the stack, it wouldn't work usually since the memory gets deallocated ones the call returns. The heap is sort of the middle ground, where you control when it gets allocated and deallocated so it's easier to optimize your memory usage.
@@CodeVault thanks a lot!!!
but globally declared array does not require pointers which consume significant amount of memory(as we can see for heap),still when the question is about like..using a fuxed amount of memory again and again and a large amount of data,i believe heap has the upper hand there!!
am i right??
TIA!!
keep up the good work,btw!
Yeah, when working with large amount of memory it is best to use the heap. As global memory will be saved to disk in the executable.
@@CodeVault Actually the proper wording should be "static", "automatic" and "dynamic", respectively. It refers to the lifetime of objects. Static are persistent, automatic are freed when the identified goes out of scope (not only when function ends) and dynamic are freed on demand. Word "global" is not technically correct because one can have static variables defined inside the function, or even a nested scope that is not accessible from outside.
love u
I have a problem I’ve been working on. The program sums up a value with a certain number of child processes. It asks the user for number of terms and child processes to split the calculation for the number of terms. I started with making a two dimensional array. How would I loop to make just four child processes and reference the pipe for the child? Example the user puts 1000 and 4 so 1000 terms and 4 child processes.
I'm not sure I understand your question. You have a 2D array and you want to know how you sum each subarray (say of 250 elements in that case) in each child process? Is that right?
@@CodeVault Yes, so i watched your video on pipes for two way communication which helped. I run into a problem when I try to use a for loop. I create the pipe based on the user input for child processes. I need to pass from the parent the binary values of the terms, number of child processes, and say j from the for loop. Then the child does it sums and passes to the next and when they're done they pass it back to the parent to get the total. So my output would be something like Child #1 approximation for sum over 0-250 Child #2 for sum 251-500... etc and the parent would print the total. The problem i have is how do I know which child process is what. I also have a hard time figuring out how to fork() just four children.
I wrote something like this
int(*fd)[2];
fd = (int(*)[])calloc(4,sizeof(fd[0]);
for(int j = 0; j < 4; j++)
pipe(fd[j])
if(fork() == 0)//Child
{
read(fd[j])???
}
else
{
//Parent?
}
Also, within my child I call a function computePartialSum()
To fork just 4 children you'd have to call fork() only in the parent process. Like this:
int pids[4] = { 0 };
for (int i = 0; i < 4; i++) {
pids[i] = fork();
if (pids[i] == 0) {
break;
}
}
So for every fork, each child process would hit the break statement and exit, while the parent would keep creating processes.
Next, to figure out which process is which you have to think about how the pids array is looking in each of the processes (suppose id1, id2, id3 and id4 are the child processes ids):
parent pids[4] is { id1, id2, id3, id4 };
child1 pids[4] is { 0, 0, 0, 0 }; since it never assigned any other value than 0
child2 pids[4] is { id1, 0, 0, 0 };
child3 pids[4] is {id1, id2, 0, 0};
child4 pids[4] is {id1, id2, id3, 0};
So, to figure out which process is which, you can just check the first occurence of 0 in the pids array:
for (int i = 0; i < 4; i++) {
if (pids[i] == 0) {
// ... Code executed only by child i
break; // Break so that it only executes once per process
}
}
I-
Love you
why does all the pointer array of int type takes 64 bytes?
although my pc id of x64 config, yet it shows 4 bytes for int type of pointer
Even though you are on a 64 bit machine you might be compiling your program for 32 bit. Also, make sure you dont' confuse int types with int* types. The former has 4 bytes and the latter has 8 bytes (on 64 bit).
@@CodeVault thanks!!
🙂🙂
@@CodeVault noo!!
i did take into account the fact of int* and it shows 4 bytes!!
however,then i checked my compiler which happens to be of 32bit!!
😅
when we use & here ?
Whenever we want to get the address of something.
For arrays we don't have to do this since it's redundant:
int arr[5];
printf("Address of array is %p", &arr);
// is the same as
printf("Address of array is %p", arr);
There's this video on the topic: code-vault.net/lesson/4rx6a7yjaz:1603820088963
and a continuation code-vault.net/lesson/wxugezii3r:1603820089003
@@CodeVault
typedef struct s_list
{
void *content;
struct s_list *next;
} t_list;
void ft_lstadd_front(t_list **lst, t_list *new)
{
if (!lst && !new)
return ;
new->next = *lst;
*lst = new;
}
int main()
{
t_list *lst = ft_lstnew("start");
t_list *student0 = ft_lstnew("student0");
t_list *student1 = ft_lstnew("student1");
ft_lstadd_front(&lst, student0);
ft_lstadd_front(&lst, student1);
}
Do you have a video about using ** and & here?
Oh no! This is not optimal. You want to allocate all elements in one block. Then all elements are still continuous in memory. Then you can pass your 2D-array into BLAS algorithms etc. without further modifications. Also if you have many rows there will be many calls to malloc. Malloc is a system call and hence very slow. If you first allocate a array of pointers, for the beginning of each row, and then allocate the elements, you get away with two allocation no matter the dimensions of the 2D-array. Doing one allocation for each row will make you program slow.
Huh, in these videos I sometimes omit certain aspects such as performance or certain good practices that are used on production-ready projects (if they take away from the subject at hand).
But this intriguing and I might consider making its own video. Just to fully understand: You mean to just allocate the pointers, allocate the block for all values (malloc(x * y * sizeof(int))) and then assign all the pointers to their respective addresses?
It's not mooltaa.... It's maaaalteaaaa
I am the 1000th person to like this video
Thank you!!