Тёмный
No video :(

Stupid C Tricks: Unsafe Functions You MUST Avoid! 

Dave's Garage
Подписаться 799 тыс.
Просмотров 61 тыс.
50% 1

Dave takes you on a tour of the most unsafe C runtime library calls and how to update your code to modern, safe standards. Features C and C++ examples. For my book "Secrets of the Autistic Millionaire": amzn.to/3diQILq
My apologies for the erroneous prototypes that indicate a return value: I stripped the return values at the last minute to simplify things, but didn't change the prototypes, proving there's "no such thing as a trivial last minute change!"
Follow me on Twitter: @davepl1968
Discord Chat w/ Myself and Subscribers: / discord .
Code: github.com/Plu...
"David's" Page on his tiny app: davidesnotes.com/
Primary Equipment (Amazon Affiliate Links):
* AppleTV 4K - amzn.to/3Web65S
* Dell Monitor - amzn.to/3Dmbsyx
* Indigo Automation Software: www.indigodomo...
* Black and Decker Stud Finder - amzn.to/3fvEMuu
* Camera: Sony FX-3 - amzn.to/3w31C0Z
* Camera Lens: 50mm F1.4 Art DG HSM - amzn.to/3kEnYk4
* Microphone: Electro-Voice RE 320 - amzn.to/37gL65g
* Teleprompter: Glide Gear TMP 100 - amzn.to/3MN2nlA
* SD Cards: Sony TOUGH - amzn.to/38QZGR9

Опубликовано:

 

21 авг 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 513   
@DavesGarage
@DavesGarage Год назад
My apologies for the erroneous prototypes that indicate a return value: I stripped the return values at the last minute to simplify things, but didn't change the prototypes, proving there's "no such thing as a trivial last minute change!"
@decky1990
@decky1990 Год назад
Read my mind!! In my head I was screaming “why is it not void!!??” 😂
@richardyao9012
@richardyao9012 Год назад
You still have the problem of describing snprintf() incorrectly. It will always write a valid string, unless you tell it the buffer is 0 length. Then it writes nothing.
@mcg6762
@mcg6762 Год назад
Please also clear up your erroneous statement that snprintf is unsafe. snprintf is safe and will not cause buffer overflow or unterminated strings. See my other top-level comment.
@shahqu5dohcoh9ri88
@shahqu5dohcoh9ri88 Год назад
I have to questions: 1. I read that the _s function are supported only by msvc and they're not standard. They were like standard in c11 if I remember correctly, but it was optional to implement them. In fact gcc and clang don't support those. Doesn't the use of those drastically reduce the portability of the code? 2. How can it be that the standard library, on which all the compilers I've written before rely on, failes to be secure? Is there a way to use it 100% safely which doesn't consist in rewriting the string library from scratch with safe _s functions?
@LordErnie
@LordErnie Год назад
I'm curious about the book. Something I'll add to my collection for sure in the near future. Any advice for when I'm reading it? Great vid btw. It really shows the evolution of C, and how a language known for its dangerous capabilities can become so friendly to use. Isn't there a performance drop in using the safe functions though?
@slippydouglas
@slippydouglas Год назад
An alternative fix for strcat_exampleC2() is to just (always) set the last character to null- szStrOut[MAX_PATH-1] = '\0’; It doesn’t matter if the string ends up being shorter; all string functions stop reading when they hit null, and setting the final character unconditionally means no branching and thus produces simple fast assembly.
@stolenlaptop
@stolenlaptop 4 дня назад
I use this truncating effect often.
@mathewbenson5447
@mathewbenson5447 Год назад
Your first two functions have a size_t return type, but there is no return call.
@DavesGarage
@DavesGarage Год назад
My apologies for the erroneous prototypes that indicate a return value: I stripped the return values at the last minute to simplify things, but didn't change the prototypes, proving there's "no such thing as a trivial last minute change!"
@tweakoz
@tweakoz Год назад
@@DavesGarage I do not recall what would happen in windows/msvc, but on Linux/gcc/clang, in my experience - not returning something when the signature specifies to do so can definitely result in a crash. I believe the crash is stack/postamble related, as opposed to some downstream bug due to returning garbage.
@stephenhookings1985
@stephenhookings1985 Год назад
It's definitely not wise to return garbage especially if someone tries to use it
@johnshaw6702
@johnshaw6702 Год назад
@@tweakoz You reminded me of a time when I tried to fix all the functions in the code I inherited to actually return something known, instead of whatever just happen to be in the ax register. It broke the code. 😂 I didn't have the time to figure out why, so I just left it alone after that. 🤔
@erikkonstas
@erikkonstas Год назад
@@johnshaw6702 Yuck, sounds like somebody knew some particular undefined behavior and used it...
@uzimonkey
@uzimonkey Год назад
_countof is not new to C because it's not in C at all. It's a Microsoft macro buried in one of their headers. Probably still best to use sizeof(x) / sizeof(x[0]) or since we know these are char arrays, just sizeof(x). Edit: Further, I don't think any of the _s functions are available on glibc. They're in C11, but in the optional annex k which they never implemented. I don't know why, but at least on my recent GCC/glibc installation they're not present.
@doBobro
@doBobro Год назад
You can try to define __STDC_WANT_LIB_EXT1__ before including string.h
@jongeduard
@jongeduard Год назад
Thanks! Dave is clearly a programmer mostly on just Windows. This is all kind of matching with my other reply here, where I explain that I never used those _s versions as well, because again, it was MSVC only suggesting these and deprecating the older ones, while general standards didn't even have those functions. This may have changed today, but I still don't believe the non-_s versions are actually deprecated according official standards. Or can anyone confirm otherwise?
@packmandudefake
@packmandudefake Год назад
@@jongeduard My K&R book copy tells it’s fine.
@cianmoriarty7345
@cianmoriarty7345 Год назад
The general convention is anything indentiers with a leading underscore is an implementation detail which might change, so if you don't own it, ignore it or everything might break.
@jongeduard
@jongeduard Год назад
@@cianmoriarty7345 I am not so sure about that explanation as the only possibility. Just think about things like __stdcall and __declspec, which really never changed and probably never will, but for which the explanation is that they are absolutely Windows-only, rather then that they are expected to change.
@mcg6762
@mcg6762 Год назад
snprintf will not overflow the buffer and it will add null termination, so it is safe. The thing to be aware of is that it returns the number of characters it would have written (excluding null terminator) if the buffer would have been large enough. That way you can check if the resulting string was truncated due to the buffer being too small.
@mcg6762
@mcg6762 Год назад
@@JS-oh2dp I think Dave got this one wrong. I don't know what he was talking about. See "man snprintf" in any Linux distro and it is clearly explained there consistent with what I wrote above.
@johnshaw6702
@johnshaw6702 Год назад
@@mcg6762 I haven't used it in years, but that sounds right. Although I would need to verify that it guarantees null termination. I'm definitely a trust but verify guy.
@richardyao9012
@richardyao9012 Год назад
@@johnshaw6702It does. We rely on it in OpenZFS because it does this.
@turdwarbler
@turdwarbler Год назад
first example also had another bug, it says it returns a size_t but there is no return statement !!!
@alakani
@alakani Год назад
And a bug in the PowerPoint.. it should have started as "Can you spot the bug?" and then added the "s" when he said "actually" :P
@beowes
@beowes Год назад
Won’t C just return whatever is in EAX? I guess the return of puts() remains? C 6.9.1, 12 If the } that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined. ?
@JCWren
@JCWren Год назад
Not to mention it didn't check the arguments for being null.
@turdwarbler
@turdwarbler Год назад
@@JCWren true, but that in itself isnt a bug. It may be bad programming, BUT if you know the parameters are never null then you dont need to check. However, it would be wise to check unless there is an overriding reason not to.
@JCWren
@JCWren Год назад
@@turdwarbler That same argument can be made for strcpy() and friends. If I >know< the incoming lengths won't exceed the buffer, it's "safe". If it's a function that's entirely local to your program, then yes, you might not need to check the arguments. If it's exposed at all, it should return an error code or at least do an assert() check.
@ikouzaki
@ikouzaki Год назад
Amazing vid. Very educational! I'd love more of this. Thanks for dishing out such high quality content.
@HaydenLikeHey
@HaydenLikeHey Год назад
As a relatively new C programmer, I find stuff like this really helpful and fascinating! Thanks for this, Dave!
@Ryan-xq3kl
@Ryan-xq3kl Год назад
Consider yourself lucky, I started 3 years ago and now have to rewrite a whole library of unsafe code xD
@lwilton
@lwilton Год назад
@@Ryan-xq3kl You can use all of the old functions safely, but you have to do ALL of the checking that is buried inside the new Windows "safe" functions. It is easy to miss checks, especially if you don't have someone to look over your shoulder and review your code and ask questions.. And the _s functions aren't a panacea, because some of them do strange things like padding the output buffer with nulls out to the full count, where the old length-specifying functions would have stopped after the input length.
@johnshaw6702
@johnshaw6702 Год назад
I learned and wrote C code on my own for years (my first real language). My one rule for coding in any language is to constantly check that it does what you think it does. You can write safe code, but the string functions are an area that you have to pay close attention to. Languages like C/C++ does not even specify what the character size is. It basically says a short is >= a character size, int >= short, long >= int, but doesn't say what size char is. That is implementation defined.
@hofertyp
@hofertyp Год назад
I remember the early 00 years sitting in programming class and my teacher just throwing that red/white kerninghan and Ritchie C book -> still to this day pointers and functions with pointers extremely scares me. Somehow I'm glad that I've started with C. It takes you much much longer but because it's more leaned towards hardware you learn so much more important background basics that let's you better understand how the software integrates with the hardware!
@ralfbaechle
@ralfbaechle Год назад
Red and white? Sounds like you're a German speaker and were given the German translation. The original English issue has a white and blue cover. I've red both and think the German version is a very careful translation by somebody who understood the subject and was very careful about certain things including pointers. It's how I learned C but from mid 80's copy about K&R C. Pascal's pointer concepts are similar but gave me the feeling somebody was doing his best to make the syntax more obscure to discourage their use. Pointers can be incredibly elegant.for building efficient data structures. Things are getting ugly once reaching the point of multithreaded code with lockless data structures scaling to many possibly thousands of processors. Few people need to care about such mindbending exercices.
@hofertyp
@hofertyp Год назад
@@ralfbaechle exactly the book was well written but still very difficult for a beginner. Barely made the exams and then we switched to JAVA and C# with .NET framework. Currently working in the IT-Sec field but as an hobby programming on Mikrocontroller like ESP32 (differnet Sensors via WiFi or LoRa) so trying to improve rusty C skills ;)
@ralfbaechle
@ralfbaechle Год назад
@@hofertyp I had some low-level programming experience by the time I read the K&R C book. Plus it was loaned so I *had* to absorb it within 24 hours which worked ok for most part. Good prep for an eventual carreers as a Linux kernel developer. Java and C# all sound useful in sense of they might land you an actual job. My own introductory course was taught in Pascal. Turbo Pascal to be accurat which probably the most popular programming language on PCs at the time.. My university observed there was a complete split between people with piror and no prior programming experience. The one groupswas able to get top grades with little effort, the other had to work hard. No middle ground. So the following years they thought they could level the field between the groups by using a different pgramming language. And of all the things they could have picked, they picked Postscript. I kid ya not. Untill the end of the semester printers were running the Sieve of Eratosthenes or other stuff printing out prime numbers on paper. That is if they were not outright spitting out the entire paper supply. The trays of the university's computer centre where people could pick up their printouts were particularly well filled that year. Of course students were supposed to test on ghostscript after that they they sent their code straight to the printer not realizing that would cause the printer's RIP to execute the code. The following year the language was change again. That timei it was LISP. ITsec can be fun as well :)
@Ryan-xq3kl
@Ryan-xq3kl Год назад
If functions with pointers scare you use a c++ smart pointer, you can automate your resource management process and have peace of mind.
@johnshaw6702
@johnshaw6702 Год назад
I actually took my one and only C programming class because I was having an issue understanding standing pointers to pointers to etcetera. One thing I liked about C is I could look at the code and practically see what the assembly would look like in my head. There is a reason why writing ++i is good practice and writing i++ is not, but that's another story.
@modolief
@modolief Год назад
Great instructive video, perfect for my needs: I'm teaching computer science to a budding software engineer and C is one of the languages I'm teaching him. I always remembered string handling in C as difficult, and am glad to have an update on the proper methodologies for doing this important task.
@frityet2840
@frityet2840 Год назад
The _s functions are not the proper methodology, and should not be used as such
@modolief
@modolief Год назад
@@frityet2840 So then ... how should we do strings nowadays?
@nicolefischer1504
@nicolefischer1504 Год назад
@@modolief As you always have. Safe code existed in C since before these functions and we don't need them now.
@tomysshadow
@tomysshadow Год назад
Visual Studio won't even let you use most of these anymore without setting a specific flag. Which is a good thing, it's certainly saved people from making these mistakes.
@richardyao9012
@richardyao9012 Год назад
If you use LLVM/Clang like Google does for Chrome, you need no such flag.
@Scotty-vs4lf
@Scotty-vs4lf Год назад
always get in my way cuz im intentionally writing unsafe code lol
@tomysshadow
@tomysshadow Год назад
@@richardyao9012 What do you mean exactly? IMO you shouldn't be using the flag anyway, at least not for new code.
@MattSuguisAsFondAsEverrr
@MattSuguisAsFondAsEverrr Год назад
it's sad that the bots have arrived
@richardyao9012
@richardyao9012 Год назад
There are uses of strcat() and strcpy() that are safe, provided that you know the lengths of the strings in advance to compare against a buffer length. There are a few of these safe uses in OpenZFS. If I recall, strcat is used to make a mount options string for a network mount in ZFS where a bunch of fixed length strings are concatenated to make the final string. The total number of bytes in the longest possible output string is smaller than the buffer length in that case.
@landonkryger
@landonkryger Год назад
I've recently been doing a ton of of string printf upgrades at work. Instead of sprintf or it's variantes, I've been using std::format and love it. It does require you to be using c++20 though.
@jope2
@jope2 Год назад
Could you clarify the issue with snprintf? You say "the size specifier is generally based on the format specifiers", which is also true with sprintf_s. If you don't have a big enough buffer, snprintf will truncate, put a null terminator, and return how many bytes it would have written if the buffer was big enough (excluding the null terminator). sprintf_s and snprintf_s do the same but sprintf_s returns how many bytes it actually wrote, and snprintf_s returns how many it would have written, same as snprintf. In all three you need to check the return value to know if the string was truncated or not, but in terms of not going off bounds and returning a null terminated string they all seem equally safe.
@richardgg2889
@richardgg2889 Год назад
On the STRTOK part: - tried both C and C++ ways and STRTOK was maybe 90% faster than using stringstream in c++ when parsing very large text files in loop.
@Gragledump
@Gragledump Год назад
yea c++ stream-related functions tend to have this problem in my experience
@perwestermark8920
@perwestermark8920 Год назад
That's because strtok() is allowed to modify the input string. So there is zero copying of data. The issue with strtok() isn't amount of RAM, but that it isn't thread-safe. It uses a global variable to keep track of where to continue. And Posix has a strtok_r() that solves that by just as strtok_s() take one parameter extra to hold the state.
@DavesGarage
@DavesGarage Год назад
It's not first one to crash wins, though. It matters more if the code is safe, THEN make it fast.
@frankhenigman5117
@frankhenigman5117 Год назад
@@DavesGarage I think your strtok explanation could be better. You say something like "partying on memory of unknown size" which, apart from being unclear, implies (to me at least) there is some kind of overflow danger. But as I understand it the only issue is thread safety (which you don't mention) and the only thing strtok_s (and strtok_r which is the same) does differently is to address that.
@turdwarbler
@turdwarbler Год назад
@@perwestermark8920 Yes strtok()isnt thread safe, but so what, that doesnt make it a bad function, it just means it isnt thread safe. If you are writing a single threaded program its perfectly safe as it has always been. Its horses for courses. Use the most appropriate code for your use case. Its like when people say, oh that code isnt portable, again, so bloody what, if I am writing code to target x86-64 who cares whether its portable or not. Too many people worry about things that dont matter. If non portable code is faster, then thats what I will use because it matters to me.
@nickwallette6201
@nickwallette6201 Год назад
This reminds me why I code almost exclusively on Linux. I really would like to learn how to write Windows programs (and I’m into retro computing, so that would be C/C++ 7.0 on Win 3.0 to VS 2000-whatever on Win 7), but I am completely lost on the quagmire of string handling and file IO calls with different char sizes, and having everything abstracted behind cryptic type definitions. I’ve tried learning this a few times, and it seems like every example says “don’t use all the other ones, just do this ...” and then directly contradicts the last one I read. It just feels like there’s standard ANSI C, and there’s ... whatever the 🌮 is going on in any given Windows source code example.
@johnshaw6702
@johnshaw6702 Год назад
I sometimes thought I should have become an MSVP, because half the things they wrote wouldn't have passed my basic test for good coding. But was useful as a starting point.
@technowey
@technowey Год назад
This was a good video. I'd forgotten, if I ever knew, that strcpy_s always added a null terminator. The bugs are very obvious to an experienced C programmer. The C++ function had a bug too. The return type was size_t, however, the function didn't return a value.
@WolvericCatkin
@WolvericCatkin Год назад
Correction: `endl` isn't just the newline character, the standard specifically specifies it should explicitly _flush_ the relevant buffer, which in the best case, is a no-op, given most buffers are designed to flush on newlines, but at worst, can be introducing unnecessary overhead if the flush is poorly implemented.
@superscatboy
@superscatboy Год назад
Absolutely correct. std::endl is honestly a curse IMO
@ufufuawa401
@ufufuawa401 Год назад
std::endl manipulator is used inorder to behaving stream line buffered when encounter newline, because std::ostream is fully buffered. C stdout is Line Buffered
@grahamheath3799
@grahamheath3799 Год назад
Useful; Reminds me of a problem. Many years ago when when the UNIX time (number of seconds since the epoch) went from 999..... to 1000..... i.e. ASCII length increased by 1. An application that used this to generate a file name suddenly started misbehaving. As a UK support group we worked out what was happening. The only difficulty was convincing the code writer that somebody without an american accent knew what was wrong with his code.
@godfreypoon5148
@godfreypoon5148 Год назад
I didn't know they had computers in the UK!
@grahamheath3799
@grahamheath3799 Год назад
@@godfreypoon5148 yes we made that leap just after we took our socks off and could count up to 14.
@4X6GP
@4X6GP Год назад
@@grahamheath3799 So they figured out how to make them leak oil?
@grahamheath3799
@grahamheath3799 Год назад
@@4X6GP ??
@russlehman2070
@russlehman2070 Год назад
@@godfreypoon5148 Joke: Q: Why don't the British manufacture computers? A: They couldn't figure out how to make them leak oil.
@skiera0000
@skiera0000 Год назад
I would love to watch more videos about C/C++ code by you!
@derlukas8182
@derlukas8182 Год назад
another problem with the first example is, that a non-void return type is declared but no value is returned. If any caller function attempted to use said return value it could lead to undefined behavior
@DocaTafner
@DocaTafner Год назад
I was a C and C++ dev back in the late 90's, self taught. After that, I told myself I would not have use of my skills because C/C++ in general is not used for developing business solutions, instead it's used for some background scrubbing of bytes and data, but definitely not a full business solution (not without a significant amount of time and effort, that is). So I turned to IT infrastructure. I haven't touched programming through all those years. This video makes me wonder why did I stop, it was all too natural to me. Thanks for the video, Dave. Perhaps you reignited something which I love.
@lonelymtn
@lonelymtn Год назад
These should be basics when starting out with C.
@TomStorey96
@TomStorey96 Год назад
OG strtok also has another flaw that makes it unsafe for threaded applications: it uses a static variable to keep track of where it finished off the previous call so that it knows where to start on the next.
@paulk314
@paulk314 Год назад
This video seems to have an incorrect description of strtok and strtok_s. The strtok_exampleC1 is actually totally fine (aside from not being multithread-safe). In the video, he says "The problem here is that it's partying[?] on your memory and you have no idea how big your memory is." This seems to be contradicted a couple of sentences later where he implies (accurately) that strtok just modifies the input string: "So by using strtok_s, we can do it safely. Rather than modifying the string in place, it provides you with another value, rest, which is where the rest of the string can be found". This contradicts what he says earlier about strtok "partying on your memory". No, it replaces any occurrences of characters from the delimiter string in the input string with null terminators and returns a pointer to the current token (see the man page). As long as the input string is null terminated (i.e. a valid C string), it's totally safe for single-threaded use. The issue with strtok is that it stores a pointer to the input string in a static variable (which is why on subsequent calls you pass a NULL pointer as the input string parameter so that it knows to continue tokenizing the same string) which isn't threadsafe. You need to use the reentrant version, strtok_r, to be threadsafe (again, see the man page). According to my quick scan of the MSDN page on strtok_s, it appears that it is the Microsoft equivalent of the POSIX strtok_r (or roughly so). Both functions use a third parameter as an input/output to store a context for tokenizing an input string instead of a static variable. Passing NULL in the first parameter indicates that it should continue tokenizing from the context pointer. This is totally threadsafe. The incorrect understanding of strtok in the video is evidenced by the name of the variable passed in as the second argument to strtok: "tok". That should be "delim" or something since it tells strtok which characters to use as delimiters, it doesn't output the next token there or anything.
@mathewbenson5447
@mathewbenson5447 Год назад
Make more 'spot the bugs'. I love these. I use my own as interview questions.
@Dizintegrator
@Dizintegrator Год назад
Loved the exaples for both plain C and C++, showing how much easier those basic things are now.
@TheRealStructurer
@TheRealStructurer Год назад
Much appreciated. On a hobby level I have dabbled with Objective C, C and C++ and Swift, and of course have had some issues. Right now deep into an Arduino project and good to be reminded of how to think and to write safe code. And for me as a hobbyist, some tools and languages are easier and safer than others
@richardblain4783
@richardblain4783 Год назад
Since you’re only printing the concatenated strings anyway, you don’t need the output buffer, or any string copy or append functions. Just puts the first string, then puts(“ “), then puts the second string.
@GamersUniverseOE
@GamersUniverseOE Год назад
This will create newlines between them tho. printf("%s %s", str1, str2) is probably better.
@richardblain4783
@richardblain4783 Год назад
I forgot (if I ever knew) that puts() adds a newline. Thanks for catching my mistake.
@oscarmilstein8026
@oscarmilstein8026 Год назад
These safer versions of the standard functions are unfortunately only implemented in MSVC and not in for example glibc. This makes code using these functions not very portable.
@perwestermark8920
@perwestermark8920 Год назад
If you are on Linux, then snprintf() is safe - it will give zero-terminated output. But will give different result for invalid formatting string, doing best effort instead of giving empty string and an error. So snprintf(buf,NELEM(buf),"%s%s",str1,str2) is a safe concatenation without access to strcat_s(). And strtok_r() is the thread-safe replacement for strtok() on Posix-compliant systems.
@DMLou
@DMLou Год назад
I was about to say... but you beat me to it. Yes, they're in the C++11 standard, but only as optional extensions. The glibc folks decided not to implement them. I don't have a link handy, but they even published a paper to the C standards committee saying why they aren't that great to begin with. I only skimmed the paper, though, so I have no opinion on whether I agree with them or not.
@askdiatom
@askdiatom Год назад
I learned something new!. Please make more of these videos!!. Thanks, Dave!
@HassassinCat
@HassassinCat Год назад
Fantastic video. Please, please, please, start a course of C/C++. The way you explain is gold, showing each step, explaining without making us feel like babies. I cant have enought of this. At least, please make more of this kind of video explaining things. Love ya
@rick_er2481
@rick_er2481 Год назад
Dave making a course would be amazing
@kensmith5694
@kensmith5694 Год назад
size_t type function has no return. You didn't check buffer lengths. You trusted the input to actually have the zero on the end.
@ailivac
@ailivac Год назад
There's also asprintf, which I think isn't part of any standard yet but is still available most places. It allocates a buffer just long enough and returns a pointer to it which you do have to free later, but you don't have to choose between wasting a bunch of memory every single time or risking some of your strings getting truncated.
@BaseNAND
@BaseNAND Год назад
You can easily do that with snprintf. If you pass it 0 as size, nothing will be written to the output (which may then also be a NULL pointer) but it still returns the size of the resulting string. With that size you can allocate a new block of memory that is large enough and then do the same snprintf call for that block of memory.
@ailivac
@ailivac Год назад
@@BaseNAND Forgot about that. Still, asprintf does it all for you in a single line that's basically impossible to get wrong.
@flatfingertuning727
@flatfingertuning727 Год назад
@@BaseNAND Note that doing so may not be safe if one is receiving a pointer to a string from code running in an untrusted context, since the length of a string could change between the two calls to snprintf.
@BaseNAND
@BaseNAND Год назад
​@@flatfingertuning727 You mean strings passed as arguments for the format specifiers? That is true, but that is also true for asprintf since it uses the method I described (only with vsnprintf because it is variadic). If you do it manually you can actually catch this case by checking the return value of the second call to (v)snprintf against the size you allocated with malloc/new/etc., since the second call will return something else than the first. asprintf does not handle this case and will always return the size of the string from the second call, not the size of the memory block which is based on the first call.
@richardblain4783
@richardblain4783 Год назад
Sounds like an invitation for a memory leak.
@garycharpenter543
@garycharpenter543 Год назад
Thanks much for this. One day all these depreciated error makers will be pulled from the libraries (not). Of course you assumed your input strings are null terminated even if larger than expected, however they can also overflow outside your memory space even while being read if they are not. Love to see an episode on string input verification.
@seancollins9745
@seancollins9745 Год назад
Dave, thank you, asa newb to C and programming in general this was very very very useful information !!!! thank you for these videos !!!
@mittelwelle_531_khz
@mittelwelle_531_khz Год назад
char s2[N+1]; strncpy(s2, s, N)[N] = '\0'; does the job very well. No new function required. BTW: there is a reason why the good ol' strncpy does not add the '\0'-byte if all available space is filled.
@vfjpl1
@vfjpl1 Год назад
THX
@xav500011
@xav500011 Год назад
Great video. Oh well I will stay in the safe waters of C# for a while longer.
@Aybex97
@Aybex97 Год назад
I started programming in C/C++ at young age, then discovered C# later on, it's a bless man ! I can't help but feel spoiled with all the goodies of that keep getting added to dotnet and C# year after year.
@taishi-sama-tfc
@taishi-sama-tfc Год назад
Try Rust first, it can be pretty difficult to learn, but all things that make Rust hard to learn is just good practices from C and C++ how to write safe(firstly memory- and thread-safe) code, enforced by compiler, such as borrow checking, lifetimes and etc. Also Rustc'a compiler errors are really helpful, and Rust language haven't heavy legacy (hello C++), so you would learn not about ways how to not shoot in your foot, but actual useful concepts. And after learning Rust this will be easy to learn about modern C++ and C
@brycemw
@brycemw Год назад
I often ended up writing some of these kinds of functions myself so I can do it the way I want from the start. I also hate how using string functions ends up with a lot of redundant calls to strlen. So I’ll often just do the strlen once and write everything else using more generic functions like memcpy
@richardrisner921
@richardrisner921 Год назад
This is a very strong argument against writing programs in C now that alternatives exist.
@richardyao9012
@richardyao9012 Год назад
How is this a strong argument? Anyone can write any code incorrectly in any language. Use functions correctly and you will not have a problem. Use a static analyzer to warn you about such functions being used incorrectly (or used at all if you are really unsure of yourself and performance overhead from using the “safe” versions where you do not need them is not a problem) and you should be fine.
@richardrisner921
@richardrisner921 Год назад
@@richardyao9012 if 80% of the standard library is deprecated and unsafe, not only is it unclear which standard functions you should be using (besides the fact that the names of those functions are esoteric), but also if you just guess you are more likely to do something unsafe than safe. At this point it is more of a puzzle game like minesweeper, where it's fun because if you are not excruciatingly careful you will blow up. For some applications C is still the only serious tool for the job at this point, but I would not want to use it if there was an alternative available that would work with me rather than against me for writing safe programs.
@WndSks
@WndSks Год назад
Correction: lstrcpynA&W will always zero terminate the string correctly.
@Unfinished80
@Unfinished80 Год назад
Thank you! This answered a lot of questions I've had about the number of string function versions.
@pdrg
@pdrg Год назад
Thanks for reminding me why I don't do C any more if I can help it!
@peterjansen4826
@peterjansen4826 Год назад
C is a fine language, sadly too many of us haven't had proper training with it which is what gives C the reputation to be a bit dangerous (software-security wise). In my case the university also is to blame, in my opinion. Why does programming get teated like any subject while it is an art. You can't become a skilled programmer in just 8/12/20 weeks, however long a period lasts at a university, teaching programming should be done in a different way than for example teachint physics or chemistry, it should be taught by being a red thread throughout the curriculum, not so much by giving assignments and not giving any feedback but by giving pointers, checking what the student has done, then explaining what a better approach would have been...You shouldn't get graded on it with an exam, you should get continued feedback. The major point is that programming iis an art and a skiill, not so much about reproducing knowledge.
@user-yh7zc9ke4s
@user-yh7zc9ke4s Год назад
the only 2 bugs in original function are: -function is size_t but there is no return -while explaining the function you didn't specify the 260 chars limitation, it should be in the docs or comment near the function MAX_PATH is used specifically for working with path in windows which is also limited, so if you somehow exceed it, your program is not going to work even with larger buffer. p.s. there is some fancy way to remove this 260 limitation, but no one does it except microsoft because 260 is always enough
@Hiram8866
@Hiram8866 Год назад
As someone who has only dabbled in C programming at a very basic level. I enjoyed this video.
@peterg76yt
@peterg76yt Год назад
When I first learned printf I was astounded at the idea of a function with absolutely no type safety, and then more astounded that I seemed to be the only person bothered by it.
@russlehman2070
@russlehman2070 Год назад
Some of my earliest efforts at programming involved C programs in DOS. DOS had no memory protection at all. Any program could access any address in memory (the whole 1mb). If you somehow messed up the null termination and then output a string, you would get some interesting results, as in, a bunch of binary garbage on the screen, usually with "MICROSOFT CORPORATION" fairly early in the garbage. Apparently that string was stored somewhere pretty close to the heap.
@perwestermark8920
@perwestermark8920 Год назад
Note that sane implementations of snprintf() is guaranteeing a zero-terminated string as long as buf is at least one character large. So same as sprintf_s(). The difference is sprintf_s() will issue an error and result in an empty string if the formatting string is invalid.
@pavelglosl8221
@pavelglosl8221 Год назад
To Dave Plummer (mainly): Can you please describe the differences between functions snprintf() vs. sprintf_s() in more detail (again)? - Because I still can't see problem with snprintf() regarding to overwriting 'foreign' RAM after end of dest. buffer. Can you show/spot some example case (situation) where it happens? - I mean particular format string altogether with combination of given parameters/variables (and of course theirs values) to be ('formatedly' - sorry for my English..) printed (out) to the destination string (buffer)? My native language is not English and I tried to play given part of your video at slower speed, but still don't fully understand it... :-( Thank you very much. Oh, and I would like to personally publish my ('own') bellowed trick how to do the same function like strncpy() but replacing it by strncat() this way: first do assign binary zero char in the very 1st byte (at index zero) of destination string/buffer and then use strncat() with right/adequate destination buffer size (most typically using sizeof() operator on dest. buff.). That's it - quite simple, isn't it? ;-)
@perwestermark8920
@perwestermark8920 Год назад
@@pavelglosl8221 BSD +C99 specifies that snprintf() reports number of characters written (or that should have been written if the buffer was big enough) excluding the terminating zero. So a return value >= the buffer size means the buffer was too small and a new buffer that is return value+1 or bigger is needed. But the produced output will be a truncated and zero-terminated string. Before c99, some snprintf() [such as before glibc 2.2] did return number of actually written characters. So no way of knowing if a larger buffer was needed. And some old snprintf() did return -1 and set errno. But no snprintf() I have ever seen have resulted in any memory overwrites or unterminated strings. Just complications figuring out how to allocate a larger buffer. With old snprintf() that returned -1, one sad way was to have a loop and double the buffer size each time -1 was returned. The variant that instead returned number of written characters was even safer since the return value doesn't indicate if any error handling is needed.
@pavelglosl8221
@pavelglosl8221 Год назад
@@perwestermark8920 Thank you a lot for your detailed explanation! I think I understand it now. More to that - I was focused mostly (and only) on possible buffer overrun/overwrite rather than considering all kinds of errors in program and it's behavior + output like David's aim to point out on unfinished/ non complete (truncated) output in text form; which other program can read and hence be dependent on it.
@perwestermark8920
@perwestermark8920 Год назад
@@pavelglosl8221 If the output is important, then function return values should always be processed and there should be some strategy for how to take care of non-recoverable errors so a user and later developer can be properly informed. If it's debug output, then truncated texts can be quite common - you get a best effort message where a smart developer focuses on most important info early. And in a multithreaded application it doesn't work well to try and split a long log output into multiple output because that can mix up the output with other thread logging. If the purpose of the output is to create text that is actually the focus of the program, then the code should be written so it can't fail unless you run out of RAM or disk. So the developer should keep track of what outputs that can be unbound aka huge and then make sure that is splitted into some loops with partial delivery. If the code produces an output file, then write to a temp file with arbitrary number of smaller writes and rename the file when last data has been written ok. The problem is that the code to check return values can make code much, much bigger. So it's common to just hope for the best. Just that this means making assumptions about what may fail. And assumptions are quite often wrong. Spelled ass-u-me for a reason... Sometimes it can help by having own primitives that are built on top of the standard functions but also includes either recovery logic or integration with the error logging functionality. So a save_file() function has either done what it can to recover from problems or have made sure there is a good error message + debug logging generated about why it could not deliver.
@Chalisque
@Chalisque Год назад
_countof is a macro that is defined in VC but not e.g. GCC. It will also give erroneous results when given a pointer. That is, char buf[64]; char *p = buf; _countof(buf); // will return something sensible _countof(p); // will not, as then the sizeof() will return the size of a pointer, instead of the number of elements in the array. Another gotcha worth pointing out.
@perwestermark8920
@perwestermark8920 Год назад
I always have a NELEM() macro in my code since 25+ years. Sizeof array divided by size of initial element. But same problem if it's a pointer and not an array. Which is why the size should be computed by the buffer owner and then propagated. One guy did change a backup function that did disk transfers of sizeof(buf) bytes. His last optimization before shipping was to change from a stack buffer to a dynamic buffer. So one night at 3 AM I did a backup on a live system where each read/write was one pointer size big. That consumed most of the system update time I had available - 2 minutes became over an hour for the backup.
@DavesGarage
@DavesGarage Год назад
I always use #define ARRAYSIZE (sizeof(x)/sizeof(x[0]))
@perwestermark8920
@perwestermark8920 Год назад
@@DavesGarage Same macro as mine. Just different name. But very handy.
@SauvikRoy
@SauvikRoy Год назад
I think _countof() would probably not compile for non-array types. Oh sorry, it's C++! ☺️
@Chalisque
@Chalisque Год назад
​@@SauvikRoy sizeof(x) is always turned into a literal integer during compilation, so e.g. sizeof(x)/sizeof(x[0]) will turn into something like 4/1 an experiment is int x; char y = sizeof(x); // compiles fine vs. int x[1000]; char y = sizeof(x); // warns about overflow I had, prior to doing this kind of experiment, thought that sizeof(x) worked like a function with return type size_t (so would have expected char y = sizeof(x) to give a type error).
@protheu5
@protheu5 Год назад
Great video as always. Would be cool to see more advanced C++ techniques.
@ralfbaechle
@ralfbaechle Год назад
So much truth in that. I inherited an old codebase which probably started off as a quick hack and from that point. And now time and time again I keep finding similar bugs. Sometimes they do bite, sometimes using various code checkers, sometimes by inspection.
@ericerpelding2348
@ericerpelding2348 Год назад
I learned C years ago using Borland's Turbo C 2.0. It seems that I have to forget all that and learn the proper, modern way to program in C/C++.
@jacoblf
@jacoblf Год назад
I wish I had you for my CS prof.
@jonweinraub
@jonweinraub Год назад
I like these educational videos a lot more than the bells and whistles of others. The speaking speed too is easier to understand. The code is concise and gave me aha moments. Now the challenge is to use it because the _s suffix is awkward.
@BoloH.
@BoloH. Год назад
Last Crusade would've had a completely different ending if they had Indy choosing the correct C string function instead of the holy grail.
@lerssilarsson6414
@lerssilarsson6414 Год назад
"Stupid C Tricks" But they are the essence of C. 😁
@realtuber6522
@realtuber6522 Год назад
strncpy is for writing to fixed length, nul padded, but non-NUL terminated string fields in structs. It really shouldn't have str in the name. The _s functions are Microsoft specific extensions that somehow got standardised under pressure for ms, but no Unix devs use them nor Unix oses support them. And strlcpy and strlcat are fine and serve the same purpose as the _s versions. Anyhow fixed size string buffers should be a thing of the past and user provided strings should be dynamically allocated.
@zyxzevn
@zyxzevn Год назад
Did most programming with Pascal. Later Object Pascal. But also some other languages. These unsafe string problems were only in C and some C++
@stomah9832
@stomah9832 Год назад
using a buffer with a maximum size is not correct. you can make it “safe” but not correct. you should first calculate the size of the output buffer and then you don’t even need the safe functions because you know the output buffer is big enough
@CT-cx8yi
@CT-cx8yi Год назад
I hope this is the start of a new playlist called "Stupid C Tricks", because I am looking forward to more videos like this.
@ambuj.k
@ambuj.k Год назад
One thing to note is that strcpy_s and strcat_s are windows only and will likely not work on Linux and glibc. Cross platform and backwards compatibility is the reason why you don't see those functions used anywhere.
@rasimbot
@rasimbot Год назад
2:28 No need to create the complete string. Substrings and space can be sent to cout in sequence
@Hauketal
@Hauketal Год назад
For all functions: check the return value. Most report errors there, even printf. Only if you have no way of handling them (like an error to fprinf(stderr, "...") during program exit), ignoring is all you can do.
@eazegpi
@eazegpi Год назад
Thanks for all these C tutorials and tricks!
@lostwizard
@lostwizard Год назад
Even worse with that first example with the strncat()s is that the "n" in strncat refers to the *source* string, not the destination. So strncat(szStrOut, szStr2, MAX_PATH-1) will still risk overflowing szStrOut.
@kc9scott
@kc9scott Год назад
Wow, I never realized that, and it’s truly awful. What you say is backed up by a quick internet search. I’ll have to check if I ever use that function.
@Guru4hire
@Guru4hire Год назад
I don't do coding except for personal interests, but if it were me (and what do I know, I am just some dude on the internet), I would pass a const string & into the function, construct a std::string_view front it, then use the find first of member function of the string_view class to find the token, use the substr function to pass the token to cout, and then use the remove prefix member function to move the front pointer in the string_view up to the postion after the delimiter. repeat until size or length = 0; Then we have to decide what to do with 0 length tokens, as there could be 2 delimiters in a row. The other assumption is that the string isn't destructed while the function is running.
@SassyToll
@SassyToll Год назад
Thank You @Dave they was excellent and I learn a lot from this. Can you make more videos like this please?, especially for C and C++. Thank you John Ireland
@gigachad6844
@gigachad6844 Год назад
This is why I love C++
@Alex-F.
@Alex-F. Год назад
And that is a great example of why I don't use standard functions/libs in my (automotive) embedded software. They simply cannot be trusted...
@josephcsible
@josephcsible Год назад
The _s family of functions are nonportable because they're from Annex K, which the standard doesn't require, and in practice basically no platforms actually implement as defined.
@DavesGarage
@DavesGarage Год назад
Try a C compiler from about 1994 or later!
@josephcsible
@josephcsible Год назад
@@DavesGarage I tried with GCC 11.3 and glibc 2.35 on Ubuntu 22.04 (all released in 2022), and the Annex K functions didn't exist there. Other than MSVC (and even it doesn't quite adhere properly to it), what mainstream compilers/toolchains do support it?
@hicknopunk
@hicknopunk Год назад
I only ever used C in DOS. Also, I only had a free 16bit complier. The big challenge was trying to make a "game" which uses over 1MB of disk storage, while only using 64kb of ram and therefore a dynamic executable which would read procedure files and overwrite parts of the 64kb of ram. Most songs, which had to be interleaved with game code would take up 20kb of ram just to drive the PC speaker. If you didn't properly guess how long code took to execute the music would have halts in it. I don't know if I was doing things wrong or not as I was self taught and didn't use libraries. At least I could specify which memory addresses were overwritten to not overwrite DOS, TSRs and the core game code to overwrite itself in ram.
@johnny_eth
@johnny_eth Год назад
As someone that's being doing C++, web tech, Java and python the last 15 years, seeing these C stdlib functions gives me some PTSD. The C API was/is hideous.
@annieworroll4373
@annieworroll4373 Год назад
I wonder how many of these unsafe behaviors were compromises in the face of the hardware they had available, or they just didn't see where it would go wrong? Which isn't necessarily a slight, when you're advancing the state of the art of something, you're going to miss at least a couple small details.
@jazzerbyte
@jazzerbyte Год назад
In the old days, function inputs were considered friendly. In the example, the original programmer was thinking in terms of "hello" and "world" and buffer sizes of 128; "no chance of an overflow". Today all function inputs have to be considered unfriendly, even in the core of the program where the inputs should have already been validated.
@MichaelButlerC
@MichaelButlerC Год назад
That book sounds interesting. Best of luck in your future!
@hrznn
@hrznn Год назад
After the first few tries I quickly learned to never ever trust standard str functions. I always write my own that goes character by character. Reverse, concat, split, per word reverse, lowercase, uppercase, shortening, extending, inserting, number conversion, whatever. Although I try to optimize them, I know it's slower than the std ones, but at least I trust my own functions.
@ReneWOlsen
@ReneWOlsen Год назад
Well the first two functions expects size_t returned .. but a value is never returned
@maciejlabanowicz8640
@maciejlabanowicz8640 Год назад
First example: there final solution should be: char szStrOut[MAX_PATH + 1], also return statement from function ismissing.
@superscatboy
@superscatboy Год назад
String manipulation in C is one massive bug.
@laserspaceninja
@laserspaceninja Год назад
Bookmarking this for later. Thanks for the tips!
@johntitor.
@johntitor. Год назад
Counting sheep don't work for me, i'll use this tonight. Cheers.
@DavesGarage
@DavesGarage Год назад
Dream of me.
@billbez7465
@billbez7465 Год назад
Thank you! Please do more videos on "can you spot the bug" theme.
@djrmarketing598
@djrmarketing598 Год назад
Its unbelievable how many programs over the past 40+ years were written the old way and were mostly fine except when hackers are injecting unexpected code. I picked up my first book K&R ANSI C at the tender young age of 11 and read it cover to cover, did every sample program in the book. Really back in the day if you were working on combining a last name and first name with a comma into a string from sanitized user inputs, everything is fine. Even on the database side, if your field lengths are set properly (and back then when we were using DBase and other ISAM databases you had storage constraints so you wouldn't just arbitrarily have 512 byte name fields), reading and using this data was fairly safe. It's really when these inputs are coming from unknown places like API calls, web calls, etc. and honestly programmers SHOULD have been checking length and not relying on assumptions. But I guess when people make a "protocol" and specify the maximum length of the incoming URL in a GET request, and developers assume that the third party is following those requirements, things get really dicey. One thing I've learned is - if there's some way to externally interact with something, you make sure everything is bulletproof. I used to get paid to figure out how to break existing embedded systems for a small company that made vending systems and guess what people in the field were doing to cheat systems. And found MANY ways people would HACK systems from cloning NFC cards and simultaneously loading/using points, to using static charge devices to short out electronics.
@flatfingertuning727
@flatfingertuning727 Год назад
Fundamentally, zero-terminated strings are a good format for a few common situations involving string literals, and are lousy for pretty much everything else. Zero-padded strings, which are what databases used, required that programmers keep track of the sizes of buffers they were using, and allow for the possibility of data precisely filling a buffer, but avoided leaking data (in languages that use fixed string buffers and don't use zero-padded strings, setting myThing to "SuperDuperSecret" and then setting it to "X" before writing it out to a disk record would write out to disk a string which, when read normally, would appear as "X", but which if inspected would also contain all but the first chracter or two of "SuperDuperSecret". Zero padding may be less efficient than letting unused bytes hold their values, but is better in situations where confidentiality may be important.
@jimadams7765
@jimadams7765 Год назад
Yes, the misappropriate use of "inheritance" in "language design". In most cases, bad (or assumed) data definitions and abstractions in the evolution from Fortran to C and other derived or variant languages. In that sense, COBOL, with its explicit "Data Division", was always going to be a superior "language" mechanism.
@zzco
@zzco Год назад
I usually just let calloc fail and crash if it gets too big, because at that point, you've lost the plot anyway and your input is likely corrupt. Crashes are bad, but are better than corrupting memory.
@Ryan-xq3kl
@Ryan-xq3kl Год назад
2:33 disproving Linus Torvald's ridiculous claim that C++ hasn't fixed any of C's issues in a few seconds.
@SmiliesGarage
@SmiliesGarage Год назад
Good thing my code is only run by about 5 people!
@brockdaniel8845
@brockdaniel8845 8 месяцев назад
Their conclusion: The design of the Bounds checking interfaces, though well-intentioned, suffers from far too many problems to correct. Using the APIs has been seen to lead to worse quality, less secure software than relying on established approaches or modern technologies. More effective and less intrusive approaches have become commonplace and are often preferred by users and security experts alike. Therefore, we propose that Annex K be either removed from the next revision of the C standard, or deprecated and then removed.
@syth-1
@syth-1 Год назад
Just saw a video couple weeks ago on how to use PowerPoint for those amazing code transitions, wasn't expecting to see it so soon .-.
@ansfridaeyowulfsdottir8095
@ansfridaeyowulfsdottir8095 Год назад
*WOOOOOSH!* is the sound of this video going over my head! 🤣 But I don't know what it is, but I find your videos and voice calming, interesting, entertaining and educational! {:o:O:}
@DavesGarage
@DavesGarage Год назад
Wow, thank you!
@joshuakb2
@joshuakb2 Год назад
This video strengthened my motivation to learn Rust
@ChrisAverageYT
@ChrisAverageYT Год назад
To be fair that's not a very good use of C++ (copies, dynamic allocations etc.). In C++23 you would rather write something like this. It's simple, safe, cheap, does null checks, compile time parameter checks, accepts char*, strings, ranges and any other compatible containers, doesn't do dynamic allocations, doesn't overallocate on stack or have hidden implementation restrictions like MAX_PATH length. void example(string_view s1, string_view s2) { print("{} {}", s1, s2); }
@erenardakaplan7984
@erenardakaplan7984 Год назад
Amazing video very informatic, thank you
@jacobusburger
@jacobusburger Год назад
Safe C feels like an oxymoron. I thought the whole point was the language being as safe as riding a rocket powered unicycle?
@RAndrewNeal
@RAndrewNeal Год назад
The safer a function, the slower it is. More safety checks and condition handling means more computing time spent. In my opinion, one should write their code in a way that makes it impossible (barring the inevitable bugs to be fixed) to overflow memory (like has been done since the beginning, right?), rather than relying on the standard library functions to do so. Also, GCC doesn't have these MS functions.
@chri-k
@chri-k Год назад
i agree completely
@ufufuawa401
@ufufuawa401 Год назад
You can limit buffer on scanf function, just use max field width with the format
@Psychx_
@Psychx_ Год назад
It's baffling that these functions have been rewritten dozens of times and yet were still unsafe.
@firstnamelastname3389
@firstnamelastname3389 Год назад
To be fair, I haven't touched anything C since college, but in my over decade of programming I've never heard str pronounced "stir", it's always just fully expand it to "string". Which I guess if you watch to the end he does do this.
@theondono
@theondono Год назад
Then your colleague calls the function with the same pointer twice and demons come out of your nose!
@CatbertDeVil
@CatbertDeVil Год назад
7:09 I was a little confused when I saw you using the word 'token' in the place of the arguments which is meant for 'delimiters'. The way I understood the term 'token' is that it refers to the extracted characters (the chunk), not the separators. Did I miss something?
@ericerpelding2348
@ericerpelding2348 Год назад
Did the authors of C, K&R, intentionally define unsafe functions for C in the 1970s? If so, why did they? Were they trying to make C a high level, "universal" assembly language?
Далее
Master Pointers in C:  10X Your C Coding!
14:12
Просмотров 299 тыс.
💀СЛОМАЛ Айфон за 5 СЕКУНД😱
00:26
PEDRO PEDRO INSIDEOUT
00:10
Просмотров 2,2 млн
Мама приболела😂@kak__oska
00:16
Просмотров 411 тыс.
Why You Should AVOID Linked Lists
14:12
Просмотров 274 тыс.
11 Characters That Crash Any PC: the Fork Bomb!
11:59
Просмотров 508 тыс.
The Pointer to Implementation (pImpl) idiom in C++
6:54
Fast Inverse Square Root - A Quake III Algorithm
20:08
Modern C++: Unique Ptrs and Vectors
16:25
Просмотров 138 тыс.
how NASA writes space-proof code
6:03
Просмотров 2,1 млн
31 nooby C++ habits you need to ditch
16:18
Просмотров 766 тыс.
💀СЛОМАЛ Айфон за 5 СЕКУНД😱
00:26