Thank you so much! I was assigned a task in college, which was to create a function that reverses a string using only pointer notation, and since it's still a difficult topic for me to understand, I had no idea how to do it. I was frustrated until I stumbled accross this video. And I have to admit, not only found the solution paired with a great explanation, but also the mindset that you describe, Divergent Thinking, was also of tremendous help! I found out I was being negligent on trying to come up with the 'perfect solution', which is never really the case. It wasn't until just now that I started doing many tests with different options that I could finally understand the logic behind this particular pointer function, gain a better understanding on the topic and better my confidence about my programming skills. Again, thank you so much! This is the kind of people who you really wanna learn from! Liked and subscribed right after watching the video!
I mean, by allowing myself to write down those other options, I could understand how the one I needed works. In other words, you first have to go through many tests or 'imperfect works' in order to get to the 'better works'. Just wanted to add that. Thanks!
I love it when someone says there are usually many ways to get something done. Usually there is only a few optimal ways to do it but that doesn't mean things can't happen lots of weird and even fun ways. Nicely done. Thank you.
Its amazing how many programmers I've talked to that have no perquisite on the basics of computer hardware such as memory and their addresses. No wonder they are having a hard time :/ They don't know that a pointer is literally a fundamental thing at nearly its lowest level of how computers operate on data :/ I think python spoils beginners and c++ scares beginners. C is easy to learn and it teaches how computers actually work.
I totally agree. Just moved into a team where all of my colleagues are "green" and they are all scared of touching C code because it's "dangerous" :) But they're really confident in Python and Go.
I would suggest using 3 XOR operations to swap characters and it would be a different way of doing things since it's not using temporary storage and is pretty fast.
@@morticiaplaiddams en.wikipedia.org/wiki/XOR_swap_algorithm Sorry, it should be 3 XOR operations to actually swap both values, 2 only recover one of them
I used a different method to do the swapping part basically used the fact that ASCII characters have numerical values " string[i] = string[i] + string[length-i]; string[length-i] = string[i] - string[length-i]; string[i]= string[i]-string[length-i];" so did this without using some temp character
When you don't understand pointers --> I hate them!!!! When you actually understand pointers----> I just love pointers, they have been so easy to understand all this time. I myself was the noob thinking the wrong way. PS.I am a Embedded Firmware Developer.
I had a big problem with executing this assignment! I kept getting a segway fault while the program compiled without an error, the texteditor did not point out any error and I could not find any flaw in the logic. What was the problem? I used a pointer to declare a string instead of an array because that is how he did Jacob did it at first. It is rather difficult to find the problem if you don't get any error, the only reason that I could figure it out is because the search engine did not fail completely when I entered "c language string segfault".
How do u put colors themes in commands, like those in your videos? It helpful visually. Exampl command char is blue, while is violet, int is blue or % is orange. U get my point
Syntax highlighting. There are a lot of different ways to get that. My editor (vscode) does it automatically for C, C++, and a lot of other languages. Nearly all editors that are designed for programmers will do this either out of the box, or with an installed plugin. Whatever editor you're using, just search for " syntax highlighting" and you'll probably find what you're looking for.
Is else encountering a segmentation error? I'm not certain why, but maybe my code can show why, but I read it might be something with the compiler writing char *str to read only memory - but why isn't Jacobs code throwing the same error? #include #include void reverse3(char *str) { char *end = str+strlen(str)-1; while (str < end) { char temp = *str; *str = *end; [segmentation fault is thrown here] *end = temp; str++; end--; } } int main(int argc, char **argv) { char *str = "smart math man"; reverse3(str); printf("here it is: %s ", str); return 0; }
String literals are constant, the memory that stores "smart math man" can't be written to by the swap assignments. It's hard to see in the video because it scrolls by fast, Jacob's main() calls strcpy() on the string literal into an array, char newstring[500] (around 11:57) and passes that array into the reverse functions.
Okay, I'll bite ... in all the examples you mention, the assumption seems to be made that a "character" is just a single byte (in the standard C tradition) rather than an entity that can be made up of multiple bytes. Consequently, no one seems to have considered that simply reversing the bytes in a string will produce incorrect or nonsense "characters" if the string happens to be made up of multibyte characters like UTF-8 Unicode. I'm too lazy to actually do any coding, but would you accept this (i.e. looking at the first byte of any unreversed remainder string to see how many bytes actually make up a "character" and then moving said number of bytes without reversal to the opposite end of the string until the unreversed remainder is empty) as yet another solution?
@@prakhargupta2960 IIRC, "w_char" is compiler dependent and so it doesn't necessarily handle the case of multi-byte UTF-8 encoded characters at all across all platforms. In the case of Microsoft compilers, I'm pretty sure that "w_char" means that the compiler assumes that all characters are 16-bits wide using the UTF-16LE encoding, not 1- or more byte UTF-8 encoded characters, as this is what virtually all of the OS API functions whose names ended with a "W" instead of "A" (for ANSI) assume.
@@lewiscole5193 I kinda get what you mean and agree with you. The whole concept of characters and strings breaks this way. A workaround could be defining your own custom data type with typedef (something like "typedef char[size] custom_type". Where size is desired size of each character. After this, we work on numerical utf values and typecast them when displaying. This however makes our lives difficult because now we need to define custom i/o methods too, but we literally asked for it. I'm pretty sure someone has already found a fix for this problem and written some nifty code somewhere. Still waiting for someone to give an official explanation as I haven't thought about the solution enough right now.
@@prakhargupta2960 If all else fails, one can presumably use UTF-32 Unicode where each character is always 32-bits wide. But aside from being wasteful of space, you still have to know whether or not the characters are stored "big endian" (UTF-32BE) or "little endian" (UTF-32LE). And then, there's the small matter of compatibility where multiple characters are concatenated together to be (in some cases) roughly equivalent to some other character encoding for some purposes, but not others. (See "Unicode normalization forms".) I'm sure that there are libraries to take care of all of the stuff and more if you're dealing with some form of Unicode, but you have to know that you are dealing with Unicode, some particular encoding of Unicode, and probably also have to be aware of how strings are dealt with by the underlying OS. You can't simply assume that you are dealing with 8-bit bytes the way C programmers have historically assumed.
@@prakhargupta2960 I'm not Professor Sorber and I don't play him on TV and so I can by no means give you an "official explanation", but I can say that one possible solution is to treat data in the way you logically think of it instead of how it might be represented in its lowest possible form. So, if you want to input a "string" from some data source, you define/write a routine that returns a "string" (and possibly a status to indicate whether or not the operation was successful) and you bury any conversion from how the OS represents strings into the sort of "string" you get back. Similarly, if you want to output a "string" to some data sink, you define/write a routine that takes in a "string" (say by away of a pointer to a descriptor [record] describing the "string" or a literal copy of the descriptor/record) and you bury any conversion from the string into whatever form the OS may use to present strings. Similarly, when you want to deal with a "character" from a "string", you define/write routines in which you somehow identify the "string" and possibly a "character position" of the character you're interested in the "string" and you bury any of the ugliness of how a "character" is represented in a "string". If you want to get rid of using a "character position", you could define a "get 1st character from a string" routine that somehow takes in a "string" (say by way of a pointer to a "string" descriptor/record) and bury in it whatever messing around (library calls) you need to make to get a unique "character" (i.e. a "normalized" UTF character if we're talking about Unicode strings). Similarly, you could then define/write a routine that takes in a "character" and appends it to the end of a "string" with any messiness having to do with operation buried so that you couldn't see it. The end result something along the lines of .... 1. Define/declare a "null" output string (i.e. a string of that has no characters in it). 2. Get a string from the data source. 3. Do until no more characters can be gotten from the data source string ... 4. Get the first character from the data source string (thereby reducing it's length by 1 character). 5. Append the character gotten to the end of the output string (thereby incrementing its length by 1 character). 6. End Do. 7. Put the output string to the data sink. 8. Go eat some pizza and drink a caffeinated beverage and wait for your grade from Professor Sorber. All of the gory details are then left as an exercise for the reader, in this case, Professor Sorber. ;-)
I never advocate recursive functions. They are strictly forbidden in software products that are safety critical devices (medical equipment, planes, self driving cars, etc...). See MISRA standards and ISO26262... a bug in a recursive function can exceed stack space precluding debugging techniques and stack dumps from being used.
Recursive functions definitely come with their risks. I typically don't use them in embedded applications where I have limited stack space, and I wouldn't use them in safety-critical devices (as you describe). But, recursive thinking can dramatically simplify some problems, and I think it's a mistake to discard recursion outright (unless all you do is safety-critical, resource-constrained, embedded work...then maybe). Generally, I think people just need to be clear about what they're getting and what they're giving up. Also, dynamic memory allocation (malloc) comes with similar dangers. I typically don't ever allocate memory dynamically in my microcontroller code-it's just too risky-but it is super useful when you can afford to take the risks.
@@JacobSorber hmmm, well, interesting reply. I have to disagree on recursive functions. They should, first, be avoided. If you feel recursive thinking simplifies a problem, one really must know or be able to justify any risks. I do agree recursive thinking does simplify problems but it also introduces issues newcomers may not know in unexpected ways. Anyway, still, nice videos. What fields have you worked in, outside academia, where you used and justified recursive functions?
@@revealingfacts4all That's cool. You're definitely entitled to your opinion. A few thoughts. Outside of academia, I have worked for two large companies (IBM and Intel) and a bunch of small companies. I've developed firmware for radar sensors, desktop software for talking with those sensors and processing the data for traffic prediction, software systems for helping automotive techs fix cars more quickly and accurately, and some web development. I've written software (mobile app and server-side) for helping medical practices gather and analyze patient data, and I built a video server for producing local news broadcasts. I've also contributed to a few different open source software projects. There's more, but that's a fair sample. I've done a lot of other things in academia. I'm not sure why you seem to discount that half of my life. :) I have typically used recursion in two cases-when dealing with a problem that is computationally recursive (involving any sort of self-similar math) or data recursive (like most file systems and hierarchical data structures). For example, try to write a program that reads files from a simple FAT disk image. It can definitely be done iteratively (and I would on an embedded microcontroller with limited memory and no MMU), but it's much simpler recursively. You write far less code, and because it's simpler, you will probably have fewer bugs. The bugs you referred to-those that cause infinite recursion and buffer overflow- aren't hard to debug on a conventional OS with MMU-support, if you know what you're looking for. At least, the recursion doesn't usually make the debugging much harder. The stack dump is deep, but pretty obvious (just a lot of identical stack frames all the way down). If the problem/data is inherently recursive, then figuring out the case you didn't handle correctly is also usually pretty simple. Another upside of recursive debugging is that each call deals with a subproblem, which can make sanity checking easier-if there are subproblems that don't make any sense. Another downside is that most static code analyzers don't reason well about recursive code. So, you have to make up for it with better testing and hopefully a lower cognitive burden on the developer.
@@JacobSorber hi Jacob, i never discounted half your life that I can tell from my feedback. I apologize if it seemed I did. I simply wanted to understand more about why one, with experience such as yourself, would advocate for recursion wondering why I have a different opinion is all. Trying to see your view point is all I wanted. Still having a hard time understanding some of what you replied with. "OS with MMU support if you know what you are looking for." Was hoping for some examples? Perhaps a good topic for a future video. Thanks for taking the time to chat and explain. And, sorry if something sounded like I was discounting anything about you.
My code is very similar and I even tried using the code from the examples but I always get "Segmentation fault (core dumped)" why is that? I thought it's some sort of IndexOutOfBounds exception but I it does it with the examples as well so ...
I have a video about how to debug seg faults. Short answer-use your debugger (gdb or lldb) to find out where exactly it's seg faulting. Then check the memory addresses and figure out which one is invalid.
Great videos.. Really appreciate all the efford. Wondering if you could discuss 'garbage collection' at some point. Is there a auto-clean up of malloc/heap variables in Clang or GCC at any time of program termination? How about manual cleaning at atexit(). Cheers,.
malloc is a feature of the C standard library, not of the compiler. On GNU/Linux systems, glibc is ubiquitous. I don't think glibc's internal bookkeeping frees all memory on exit, since any modern kernel (in most cases, Linux) will free all memory of processes that exit.
Ah, you're right. Went too quickly on that one. It won't work correctly on odd lengths. Need to copy that middle character straight over. Thanks for pointing that out.
It's just automatically obfuscated code. Just search for "C code online obfuscator" and you'll find some options. The tools just transform the code in random ways that don't change its behavior, but make it harder to read. Identifiers are changed (some variable called, password, gets changed to something like "b8she0234t"), and code like "x = x+1;" could be changed to "x = 22+x-21;" If you apply enough of these transformations, you end up with something like reverse0a.
maybe you're passing the string as 'char *'. Try to initialize the original string as 'char str[] = "Whatever"'. Reason is that the pointer is not a vector. It's pointing to a kind of 'readonly' memory area reserved by the compiler. If you initialize the string as array it's no longer marked as read-only by the compiler.
My solution is similar to Julien's, except it doesn't need the length argument. void reverse(char *oldStr, char *newStr) { int reverseOffset = strlen(oldStr); int incrementOffset = 0; newStr[reverseOffset] = '\0'; // Null byte while (--reverseOffset != -1) newStr[incrementOffset++] = oldStr[reverseOffset]; }
Note on the margin: neither of the suggested solutions qualify for "real pro" (TM;) software development. In _real sw dev_ for low-level functions like this, there are libraries with generic interfaces and specific highly optimized implementation for each target architecture. Data is being read and written in hardware word length chunks, temporaries are held in CPU registers. If possible and useful, vector instruction set is used. An attempt to standardize how this is done and make powerful concepts easier to use is C++.
This is the thing I find maddeningly confusing - function reverse0 takes a pointer to a string as an argument, and yet within the body of the function we can immediately treat it as a not-pointed-to piece of data and even call strlen on it. What have I missed?
C is a low level language and there's not such a thing like a build-in "string" type; as soon as you need "string" you must include "string.h", and when you see the prototype of functions in this standard lib, they only use "char*"; a string in C, it's just a pointer to the first character of that string; and you must check the NULL character to know the end of the string;
@@JacobSorber yep to reverse strings. So if we do this: while (fgets(line, MAX, stdin) != NULL) { and then have a while loop which stores each individual character and then another while loop which prints it all backwards. I can send a sample code if you want.
utf-8 is variable length, sometimes it can be 8bits, sometimes 16, 24 or 32. en.m.wikipedia.org/wiki/UTF-8#encoding char is always referring to 1 byte of data. you would need to implement the bitmasking to determine how many bytes to read then move say possible 2/3/4 byte "characters" while avoiding overwriting the stuff you are shifting
I'm not a C programmer, but here is another way I came up while watching this video: 1. Get length of string. 2. Create a new variable and allocate enough memory with length of string. 3. Copy each char in a loop from position determined by length minus current index.
The pointer version is the cleanest of all. C has been made to work with pointers. Arrays are syntactic sugar for pointers: array[x] is actually *(array + x)
i feel stupid, although i programming for 11 years, being stable job at corporate made me become like plant..find the fastest and easiest way and just work to meet deadline.
Recursion should never be used unless strictly necessary. It makes the stack look like Inception movie on steroids and any little mistake means stack overflow. And it is slower. But yea, looks edgy af
That wouldn't work. `sizeof(str)` will *not* give the length of the array, but the size of a pointer. So it will be `8` in each iteration. (Or 4, on 32-bit machines.) This will lead to an incorrect result in case of very short strings, and a segmentation fault for longer ones (when it attempts to read `str[8-12+3]`, `str[8-13+3]`, `str[8-14+3]` and so on).
5:50 I initially had no idea that you could increment and decrement multiple variables in a for loop in C. Thank you so much for showing that to me!
Thank you so much! I was assigned a task in college, which was to create a function that reverses a string using only pointer notation, and since it's still a difficult topic for me to understand, I had no idea how to do it.
I was frustrated until I stumbled accross this video. And I have to admit, not only found the solution paired with a great explanation, but also the mindset that you describe, Divergent Thinking, was also of tremendous help!
I found out I was being negligent on trying to come up with the 'perfect solution', which is never really the case. It wasn't until just now that I started doing many tests with different options that I could finally understand the logic behind this particular pointer function, gain a better understanding on the topic and better my confidence about my programming skills.
Again, thank you so much! This is the kind of people who you really wanna learn from! Liked and subscribed right after watching the video!
I mean, by allowing myself to write down those other options, I could understand how the one I needed works. In other words, you first have to go through many tests or 'imperfect works' in order to get to the 'better works'. Just wanted to add that. Thanks!
I love it when someone says there are usually many ways to get something done. Usually there is only a few optimal ways to do it but that doesn't mean things can't happen lots of weird and even fun ways. Nicely done. Thank you.
Been learning “Modern C++” for the past 3 months, that pointers segment at 6:43 was so delicious! 🤤
I do what I can. Glad you enjoyed it.
@@JacobSorber dammit that pointers segment is GOLDEN
My friend who is just starting to learn C sent me this video and reminded me of this absolute masterpiece at 6:42 😂
Its amazing how many programmers I've talked to that have no perquisite on the basics of computer hardware such as memory and their addresses. No wonder they are having a hard time :/
They don't know that a pointer is literally a fundamental thing at nearly its lowest level of how computers operate on data :/
I think python spoils beginners and c++ scares beginners. C is easy to learn and it teaches how computers actually work.
I totally agree.
Just moved into a team where all of my colleagues are "green" and they are all scared of touching C code because it's "dangerous" :)
But they're really confident in Python and Go.
You are a great teacher. From a guy who spent his who le life in tech.
I would suggest using 3 XOR operations to swap characters and it would be a different way of doing things since it's not using temporary storage and is pretty fast.
oh man that is brutal and also works! Sort of thing you would do with inline assembly however.
Ah, that's a great one. Sorry I missed it.
Could someone who understands please elaborate on how this method works? How are XOR operations used to swap letters?
@@morticiaplaiddams en.wikipedia.org/wiki/XOR_swap_algorithm
Sorry, it should be 3 XOR operations to actually swap both values, 2 only recover one of them
@@JacobSorber Ah, sorry, just realized it should be 3 XOR operations...
This one video teaches SO much. :)
Great format.
I used a different method to do the swapping part
basically used the fact that ASCII characters have numerical values
" string[i] = string[i] + string[length-i];
string[length-i] = string[i] - string[length-i];
string[i]= string[i]-string[length-i];"
so did this without using some temp character
The second idea with i and j was exactly the solution I came up with independently
Could you make videos on embedded system programming
Yes, it's on my list of topics. It's just a matter of finding time.
@@JacobSorber Something on embedded RTOS is also highly appreciated. Love your work. :)
Dude you just nailed it with the pointers ,i was waiting for that ,hahaha.
Maybe in swap 7 instead of malloc and memset use calloc ?what do you think
When you don't understand pointers -->
I hate them!!!!
When you actually understand pointers---->
I just love pointers, they have been so easy to understand all this time. I myself was the noob thinking the wrong way.
PS.I am a Embedded Firmware Developer.
I had a big problem with executing this assignment! I kept getting a segway fault while the program compiled without an error, the texteditor did not point out any error and I could not find any flaw in the logic. What was the problem? I used a pointer to declare a string instead of an array because that is how he did Jacob did it at first. It is rather difficult to find the problem if you don't get any error, the only reason that I could figure it out is because the search engine did not fail completely when I entered "c language string segfault".
How do u put colors themes in commands, like those in your videos? It helpful visually. Exampl command char is blue, while is violet, int is blue or % is orange. U get my point
Syntax highlighting. There are a lot of different ways to get that. My editor (vscode) does it automatically for C, C++, and a lot of other languages. Nearly all editors that are designed for programmers will do this either out of the box, or with an installed plugin. Whatever editor you're using, just search for " syntax highlighting" and you'll probably find what you're looking for.
@@JacobSorber Thnks. I will continue learning the basic instroduction of C with u. Keep up the good work!!
Is else encountering a segmentation error? I'm not certain why, but maybe my code can show why, but I read it might be something with the compiler writing char *str to read only memory - but why isn't Jacobs code throwing the same error?
#include
#include
void reverse3(char *str) {
char *end = str+strlen(str)-1;
while (str < end) {
char temp = *str;
*str = *end;
[segmentation fault is thrown here]
*end = temp;
str++;
end--;
}
}
int main(int argc, char **argv) {
char *str = "smart math man";
reverse3(str);
printf("here it is: %s
", str);
return 0;
}
String literals are constant, the memory that stores "smart math man" can't be written to by the swap assignments. It's hard to see in the video because it scrolls by fast, Jacob's main() calls strcpy() on the string literal into an array, char newstring[500] (around 11:57) and passes that array into the reverse functions.
@@diceandbricks Thanks for the reply; I noticed that a little later. Sure enough, newstring works fine. Well, I learned something new today.
My "go to Learn C more"channel
thanks bro i lOve C programming
Me to. You're welcome.
me too
AwYea me too
Just implement stack, push() all chars in, pop() them and you're done 🙂
But that's bad in space and time complexity.
@@philperry6564 It's basically the same as using recursion.
@@Uerdue Yesn't.
I really like these approaches👍 great teaching.
great content!!! but where can i find the instrumental in the background????
Okay, I'll bite ... in all the examples you mention, the assumption seems to be made that a "character" is just a single byte (in the standard C tradition) rather than an entity that can be made up of multiple bytes.
Consequently, no one seems to have considered that simply reversing the bytes in a string will produce incorrect or nonsense "characters" if the string happens to be made up of multibyte characters like UTF-8 Unicode.
I'm too lazy to actually do any coding, but would you accept this (i.e. looking at the first byte of any unreversed remainder string to see how many bytes actually make up a "character" and then moving said number of bytes without reversal to the opposite end of the string until the unreversed remainder is empty) as yet another solution?
perhaps use a different data type like w_char instead of normal char in case of unicode formats?
@@prakhargupta2960
IIRC, "w_char" is compiler dependent and so it doesn't necessarily handle the case of multi-byte UTF-8 encoded characters at all across all platforms.
In the case of Microsoft compilers, I'm pretty sure that "w_char" means that the compiler assumes that all characters are 16-bits wide using the UTF-16LE encoding, not 1- or more byte UTF-8 encoded characters, as this is what virtually all of the OS API functions whose names ended with a "W" instead of "A" (for ANSI) assume.
@@lewiscole5193 I kinda get what you mean and agree with you. The whole concept of characters and strings breaks this way.
A workaround could be defining your own custom data type with typedef (something like "typedef char[size] custom_type". Where size is desired size of each character. After this, we work on numerical utf values and typecast them when displaying.
This however makes our lives difficult because now we need to define custom i/o methods too, but we literally asked for it. I'm pretty sure someone has already found a fix for this problem and written some nifty code somewhere.
Still waiting for someone to give an official explanation as I haven't thought about the solution enough right now.
@@prakhargupta2960
If all else fails, one can presumably use UTF-32 Unicode where each character is always 32-bits wide.
But aside from being wasteful of space, you still have to know whether or not the characters are stored "big endian" (UTF-32BE) or "little endian" (UTF-32LE).
And then, there's the small matter of compatibility where multiple characters are concatenated together to be (in some cases) roughly equivalent to some other character encoding for some purposes, but not others.
(See "Unicode normalization forms".)
I'm sure that there are libraries to take care of all of the stuff and more if you're dealing with some form of Unicode, but you have to know that you are dealing with Unicode, some particular encoding of Unicode, and probably also have to be aware of how strings are dealt with by the underlying OS.
You can't simply assume that you are dealing with 8-bit bytes the way C programmers have historically assumed.
@@prakhargupta2960
I'm not Professor Sorber and I don't play him on TV and so I can by no means give you an "official explanation", but I can say that one possible solution is to treat data in the way you logically think of it instead of how it might be represented in its lowest possible form.
So, if you want to input a "string" from some data source, you define/write a routine that returns a "string" (and possibly a status to indicate whether or not the operation was successful) and you bury any conversion from how the OS represents strings into the sort of "string" you get back.
Similarly, if you want to output a "string" to some data sink, you define/write a routine that takes in a "string" (say by away of a pointer to a descriptor [record] describing the "string" or a literal copy of the descriptor/record) and you bury any conversion from the string into whatever form the OS may use to present strings.
Similarly, when you want to deal with a "character" from a "string", you define/write routines in which you somehow identify the "string" and possibly a "character position" of the character you're interested in the "string" and you bury any of the ugliness of how a "character" is represented in a "string".
If you want to get rid of using a "character position", you could define a "get 1st character from a string" routine that somehow takes in a "string" (say by way of a pointer to a "string" descriptor/record) and bury in it whatever messing around (library calls) you need to make to get a unique "character" (i.e. a "normalized" UTF character if we're talking about Unicode strings).
Similarly, you could then define/write a routine that takes in a "character" and appends it to the end of a "string" with any messiness having to do with operation buried so that you couldn't see it.
The end result something along the lines of ....
1. Define/declare a "null" output string (i.e. a string of that has no characters in it).
2. Get a string from the data source.
3. Do until no more characters can be gotten from the data source string ...
4. Get the first character from the data source string (thereby reducing it's length by 1 character).
5. Append the character gotten to the end of the output string (thereby incrementing its length by 1 character).
6. End Do.
7. Put the output string to the data sink.
8. Go eat some pizza and drink a caffeinated beverage and wait for your grade from Professor Sorber.
All of the gory details are then left as an exercise for the reader, in this case, Professor Sorber. ;-)
I never advocate recursive functions. They are strictly forbidden in software products that are safety critical devices (medical equipment, planes, self driving cars, etc...). See MISRA standards and ISO26262... a bug in a recursive function can exceed stack space precluding debugging techniques and stack dumps from being used.
Recursive functions definitely come with their risks. I typically don't use them in embedded applications where I have limited stack space, and I wouldn't use them in safety-critical devices (as you describe). But, recursive thinking can dramatically simplify some problems, and I think it's a mistake to discard recursion outright (unless all you do is safety-critical, resource-constrained, embedded work...then maybe). Generally, I think people just need to be clear about what they're getting and what they're giving up.
Also, dynamic memory allocation (malloc) comes with similar dangers. I typically don't ever allocate memory dynamically in my microcontroller code-it's just too risky-but it is super useful when you can afford to take the risks.
@@JacobSorber hmmm, well, interesting reply. I have to disagree on recursive functions. They should, first, be avoided. If you feel recursive thinking simplifies a problem, one really must know or be able to justify any risks. I do agree recursive thinking does simplify problems but it also introduces issues newcomers may not know in unexpected ways. Anyway, still, nice videos. What fields have you worked in, outside academia, where you used and justified recursive functions?
@@revealingfacts4all That's cool. You're definitely entitled to your opinion.
A few thoughts.
Outside of academia, I have worked for two large companies (IBM and Intel) and a bunch of small companies. I've developed firmware for radar sensors, desktop software for talking with those sensors and processing the data for traffic prediction, software systems for helping automotive techs fix cars more quickly and accurately, and some web development. I've written software (mobile app and server-side) for helping medical practices gather and analyze patient data, and I built a video server for producing local news broadcasts. I've also contributed to a few different open source software projects. There's more, but that's a fair sample.
I've done a lot of other things in academia. I'm not sure why you seem to discount that half of my life. :)
I have typically used recursion in two cases-when dealing with a problem that is computationally recursive (involving any sort of self-similar math) or data recursive (like most file systems and hierarchical data structures). For example, try to write a program that reads files from a simple FAT disk image. It can definitely be done iteratively (and I would on an embedded microcontroller with limited memory and no MMU), but it's much simpler recursively. You write far less code, and because it's simpler, you will probably have fewer bugs. The bugs you referred to-those that cause infinite recursion and buffer overflow- aren't hard to debug on a conventional OS with MMU-support, if you know what you're looking for. At least, the recursion doesn't usually make the debugging much harder. The stack dump is deep, but pretty obvious (just a lot of identical stack frames all the way down). If the problem/data is inherently recursive, then figuring out the case you didn't handle correctly is also usually pretty simple.
Another upside of recursive debugging is that each call deals with a subproblem, which can make sanity checking easier-if there are subproblems that don't make any sense. Another downside is that most static code analyzers don't reason well about recursive code. So, you have to make up for it with better testing and hopefully a lower cognitive burden on the developer.
@@JacobSorber hi Jacob, i never discounted half your life that I can tell from my feedback. I apologize if it seemed I did. I simply wanted to understand more about why one, with experience such as yourself, would advocate for recursion wondering why I have a different opinion is all. Trying to see your view point is all I wanted. Still having a hard time understanding some of what you replied with. "OS with MMU support if you know what you are looking for." Was hoping for some examples? Perhaps a good topic for a future video. Thanks for taking the time to chat and explain. And, sorry if something sounded like I was discounting anything about you.
@@revealingfacts4all No worries. I was just joking around. Yeah, I'll see what I can do in future videos.
My code is very similar and I even tried using the code from the examples but I always get "Segmentation fault (core dumped)" why is that? I thought it's some sort of IndexOutOfBounds exception but I it does it with the examples as well so ...
I have a video about how to debug seg faults. Short answer-use your debugger (gdb or lldb) to find out where exactly it's seg faulting. Then check the memory addresses and figure out which one is invalid.
@@JacobSorber fixed, very much appreciated, sir!
7:40 might not work, bcz you cannot move array’s original pointer, only if you create another “sliding pointer”
Okay, nevermind, this is a)genius b) weird :0
Yeah, weird was the goal. :)
Recursion is limited by the size of your call stack. If the string is large the it's game over
Great videos.. Really appreciate all the efford. Wondering if you could discuss 'garbage collection' at some point. Is there a auto-clean up of malloc/heap variables in Clang or GCC at any time of program termination? How about manual cleaning at atexit(). Cheers,.
malloc is a feature of the C standard library, not of the compiler. On GNU/Linux systems, glibc is ubiquitous. I don't think glibc's internal bookkeeping frees all memory on exit, since any modern kernel (in most cases, Linux) will free all memory of processes that exit.
reverse7 was overkill!!!
How does reverse7 put the middle character in a string with an odd length?
Ah, you're right. Went too quickly on that one. It won't work correctly on odd lengths. Need to copy that middle character straight over. Thanks for pointing that out.
reverse0a is what?
is there any tool to henerate such code
It's just automatically obfuscated code. Just search for "C code online obfuscator" and you'll find some options. The tools just transform the code in random ways that don't change its behavior, but make it harder to read. Identifiers are changed (some variable called, password, gets changed to something like "b8she0234t"), and code like "x = x+1;" could be changed to "x = 22+x-21;" If you apply enough of these transformations, you end up with something like reverse0a.
My love for recursion can be described as this:
Fuck recursion
Fuck recursion
Fuck recursion
Fuck recursion
ERROR: StackOverflow Exception
I get a segmentation fault in reverse0, in line 27 and I don't get why! Any clues?
maybe you're passing the string as 'char *'. Try to initialize the original string as 'char str[] = "Whatever"'. Reason is that the pointer is not a vector. It's pointing to a kind of 'readonly' memory area reserved by the compiler. If you initialize the string as array it's no longer marked as read-only by the compiler.
Try to run reverse0 and it gives me a " segmentation fault".
thanks !
My solution is similar to Julien's, except it doesn't need the length argument.
void reverse(char *oldStr, char *newStr)
{
int reverseOffset = strlen(oldStr);
int incrementOffset = 0;
newStr[reverseOffset] = '\0'; // Null byte
while (--reverseOffset != -1)
newStr[incrementOffset++] = oldStr[reverseOffset];
}
reverse7() takes to prize for me
Note on the margin: neither of the suggested solutions qualify for "real pro" (TM;) software development.
In _real sw dev_ for low-level functions like this, there are libraries with generic interfaces and specific highly optimized implementation for each target architecture.
Data is being read and written in hardware word length chunks, temporaries are held in CPU registers. If possible and useful, vector instruction set is used.
An attempt to standardize how this is done and make powerful concepts easier to use is C++.
Aha
Will reverse3 work for both, little and big endianness machines? I don't think so(just a guess, I did not try)
it will because when you copy character by character (as all the examples do), the endianness doesn't matter.
great video ty
Thanks.
This is the thing I find maddeningly confusing - function reverse0 takes a pointer to a string as an argument, and yet within the body of the function we can immediately treat it as a not-pointed-to piece of data and even call strlen on it. What have I missed?
C is a low level language and there's not such a thing like a build-in "string" type;
as soon as you need "string" you must include "string.h", and when you see the prototype of functions in this standard lib, they only use "char*";
a string in C, it's just a pointer to the first character of that string; and you must check the NULL character to know the end of the string;
Can't you also use while loops with fgets?
Are we still talking about reversing strings? In general, yes, you can use while loops and fgets together in the same program.
@@JacobSorber yep to reverse strings. So if we do this:
while (fgets(line, MAX, stdin) != NULL) {
and then have a while loop which stores each individual character and then another while loop which prints it all backwards. I can send a sample code if you want.
Also on a side note, it would be amazing if you could make a video on using getchar, putchar, fgets and fputs.
Use xor xor xor (&x3) to switch data between vars without using a temp variable
Plz like this
That was fun.
yeah... that's me... the pointer lover... baby...
I thought so. 😉
Don't think any of these solutions would work with multi-byte characters such as utf-8
Why not? These solutions even work on your typical 64-bit integers.
utf-8 is variable length, sometimes it can be 8bits, sometimes 16, 24 or 32.
en.m.wikipedia.org/wiki/UTF-8#encoding
char is always referring to 1 byte of data. you would need to implement the bitmasking to determine how many bytes to read then move say possible 2/3/4 byte "characters" while avoiding overwriting the stuff you are shifting
I'm not a C programmer, but here is another way I came up while watching this video:
1. Get length of string.
2. Create a new variable and allocate enough memory with length of string.
3. Copy each char in a loop from position determined by length minus current index.
I find the 1st solution is the best one , clean & simple code + space optimization
The pointer version is the cleanest of all.
C has been made to work with pointers. Arrays are syntactic sugar for pointers: array[x] is actually *(array + x)
i feel stupid, although i programming for 11 years, being stable job at corporate made me become like plant..find the fastest and easiest way and just work to meet deadline.
Way too advanced for a beginner but appreciated.
Recursion should never be used unless strictly necessary. It makes the stack look like Inception movie on steroids and any little mistake means stack overflow. And it is slower. But yea, looks edgy af
Angry fast fourier transform noises
Another one, use a stack
reverse7 is like a "bogo" swap lol
void str_reverse(const char* str, char* reversed) {
for (int i = strlen(str)-1, j = 0; i >= 0; i--, j++) {
reversed[j] = str[i];
}
reversed[strlen(str)] = '\0';
}
int main()
{
char str[] = "reversed";
char rev[strlen(str)+1];
str_reverse(str, rev);
printf("%s == %s
", str, rev);
return 0;
}
Hello, here is my solution;
void reverseString(char str[], char newString[100]){
int offset = 0;
do{
newString[offset] = str[sizeof(str)-offset+3];
offset++;
}while(str[offset] != 0);
}
That wouldn't work.
`sizeof(str)` will *not* give the length of the array, but the size of a pointer. So it will be `8` in each iteration. (Or 4, on 32-bit machines.)
This will lead to an incorrect result in case of very short strings, and a segmentation fault for longer ones (when it attempts to read `str[8-12+3]`, `str[8-13+3]`, `str[8-14+3]` and so on).
If this isn’t done recursively then I will downvote this video.