a strange but powerful interview question

Sdílet
Vložit
  • čas přidán 20. 01. 2024
  • Which way does the stack grow? What even IS a stack? In this video I'll talk about an interview question that still haunts me to this day.
    🏫 COURSES 🏫 Learn to code in C at lowlevel.academy
    📰 NEWSLETTER 📰 Sign up for our newsletter at mailchi.mp/lowlevel/the-low-down
    🛒 GREAT BOOKS FOR THE LOWEST LEVEL🛒
    Blue Fox: Arm Assembly Internals and Reverse Engineering: amzn.to/4394t87
    Practical Reverse Engineering: x86, x64, ARM, Windows Kernel, Reversing Tools, and Obfuscation : amzn.to/3C1z4sk
    Practical Malware Analysis: The Hands-On Guide to Dissecting Malicious Software : amzn.to/3C1daFy
    The Ghidra Book: The Definitive Guide: amzn.to/3WC2Vkg
    🔥🔥🔥 SOCIALS 🔥🔥🔥
    Low Level Merch!: lowlevel.store/
    Follow me on Twitter: / lowleveltweets
    Follow me on Twitch: / lowlevellearning
    Join me on Discord!: / discord
  • Věda a technologie

Komentáře • 1K

  • @embeddedbastler6406
    @embeddedbastler6406 Před 6 měsíci +1681

    Technically, we also make the assumtion that the architecture even *has* a stack. The C standard does not require a stack to be present at all. In fact the word "stack" is not even metioned in the C standard one single time.
    Instead the C standard requires the architecture to have some kind of mechanism to automatically allocate local variables. But this does not have to be a stack.

    • @dasten123
      @dasten123 Před 6 měsíci +12

      But doesn't the fact that you can do recursion automatically mean that there is a stack?

    • @PTh0rn
      @PTh0rn Před 6 měsíci +144

      @@dasten123 no, frames could be allocated on the heap and freed automatically on return. some virtual machines do this iirc

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +63

      @@CallousCoder But it is not something that the C language requires. The C standard makes no assumptions about which target platform and what compiler will be used.
      E.g. I can compile my code with compiler options to randomize order of things that are usually allocated on the stack. Or even have a system which uses two or more stacks for data of different size and type. A well-formed program (without UB) without pieces relying on implementation-specific behavior will still be running correctly.
      Any solution to the question of the video is bound to rely on assumptions about implementation-specific behavior. I.e. such a solution will not be fully governed by what the C standard dictates about correct programs written in C.

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +53

      @@PTh0rn Hardware may even have multiple separate stacks for e.g. code and data. It is not so exotic: x86's CET extension maintains a separate isolated stack for return addresses. If one has multiple stacks, then these stacks may theoretically grow in different directions, and the question of the video becomes invalid.

    • @CallousCoder
      @CallousCoder Před 6 měsíci +11

      @@GrigoryRechistovYes I totally agree! It is more dictated by the ABI of the OS and even in a lesser extend by the hardware -- the hardware can help, but there's no requirement to use it. The OS ABI is more decisive in this.

  • @OMGclueless
    @OMGclueless Před 6 měsíci +230

    All of your solutions use undefined behavior. The compiler is free to do whatever it wants because pointer comparison operators between objects that are not from the same array are undefined behavior.

    • @Gregorius421
      @Gregorius421 Před 5 měsíci +49

      That's probably the reason why the mistakenly inverted condition still returned the expected result 😀

    • @AndrewLenharth
      @AndrewLenharth Před 5 měsíci +60

      Not only are they all undefined behavior, the attempted use of volatile was incorrect and gcc and clang remove the recursion and the code just winds up comparing two locations in the same stack frame anyway.

    • @npip99
      @npip99 Před 5 měsíci +5

      How do you check which way the stack grows then? Obviously we assume the stack grows in a particular direction.
      C doesn't require that at all.

    • @OMGclueless
      @OMGclueless Před 5 měsíci +14

      @@npip99 It's possible to avoid the UB by casting the pointers to intptr_t before comparing them. Then the best we can say that *if* the compiler has not inlined the function call and *if* the target ABI uses a stack and *if* the stack grows monotonically in one direction, then the function will return the right answer.

    • @Gregorius421
      @Gregorius421 Před 5 měsíci

      ​@@npip99Alternatives:
      1. No need for recursion, use another function and add `___attribute___ ((noinline))` to avoid inlining. Works with gcc/clang.
      2. Turn off optimization with `gcc -O0` (dash, capital O, zero). This also stops the compiler from reversing the order of local variables (as it happened in the video).
      3. I'd use just one function and `alloca(10000)` (it's like malloc, but on the stack) because gcc does not change the order of alloca() -s:
      #include
      #include
      #include
      int main() {
      intptr_t a1 = (intptr_t)alloca(30000);
      intptr_t a2 = (intptr_t)alloca(20000);
      printf("a2-a1: %d
      ", a2-a1);
      }
      About undefined behavior (UB):
      Although the standard says to avoid UB, the reality of life is that we encounter UB regularly (the foundation of the C standard was designed in a different era, before we had much experience with portability and what works). Avoiding it is not always feasible, but mostly it just goes unnoticed.
      Each compiler has their own (maybe different) solution when implementing UB, so we can't assume what the compiler will do, but have to dig deep and find out. Different compilers will need different solutions to solve this challenge. Of course this effort often stops at the phase "it works on my computer" 😃

  • @andreys7944
    @andreys7944 Před 6 měsíci +291

    the C standard (ISO/IEC 9899) does not explicitly mandate the use of a stack for local variables. The standard describes the abstract machine, and the choice of using a stack or another method for managing local variables is left to the implementation.

    • @ante646
      @ante646 Před 6 měsíci +2

      no idea what the best solution is here - my naive guess is to ask about the ABI (specifically calling conventions), write a function with many parameters in the signature until they are no longer passed via register, then add another two parameters such that those two values are guaranteed to be passed by being pushed onto the stack, subtract their addresses (operand order dependant on calling convention), then return a value based on the sign of the subtraction result - abysmal style but ugly and correct is better than stylish and wrong right? (could honestly be ugly and wrong for all I know)
      edit: this is naive af considering access to ABI would contain the answer, down below someone suggested examining stack pointer directly via asm which is stylish and correct

    • @AvenDonn
      @AvenDonn Před 6 měsíci +1

      I don't remember, but do you even have to have a stack at all?

    • @quazar-omega
      @quazar-omega Před 6 měsíci +30

      Academic C be like: the compiler implementation is left as an exercise to the reader

    • @cyrilemeka6987
      @cyrilemeka6987 Před 5 měsíci +2

      ​@quazar-omega 😂

    • @darrennew8211
      @darrennew8211 Před 5 měsíci +2

      @@ante646 It's UB to compare pointers from two different allocations.

  • @tunafllsh
    @tunafllsh Před 6 měsíci +453

    I don't have much practical knowledge in C, but It's good to know what I've learnt in my university was enough to come up with the same reliable solution to this problem.

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +37

      This is not a reliable solution. A compiler may still optimize away this recursive call because it can prove that the depth of recursion will always be two levels. So there is no guarantee that desired number of stack frames will be created. Not to mention that neither the presence of stack nor regular allocation strategy for placing objects on it are guaranteed at all.

    • @plesleron
      @plesleron Před 6 měsíci +4

      @@GrigoryRechistov wouldn't the creation of stack frames (and thus their order in memory in this example) count as observable behavior that the compiler couldn't optimize away?

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +13

      ​@@plesleron No, for at least two reasons.
      1. No guarantees exist that a stack frame will be created for a given function call. Inlined functions have no own stack frame, and the compiler is free to inline any function (not just those marked with "inline" keyword). Recursive functions are not exempt from it. A compiler may partially inline recursive call or specializations of the function. E.g. I know for a fact that GCC will replace tail recursive calls with jumps when the conditions are right. All such tail-called functions will share the same stack frame.
      2. You cannot access (neither read nor write) stack frames from a C program in a portable way. Their existence is not observable. No standard library call is defined for accessing them, and relying on a third-party library or inline assembly or such is outside the well defined portable behavior (i.e. it is not UB, thankfully, but the result is unspecified or implementation-defined).

    • @tunafllsh
      @tunafllsh Před 6 měsíci +2

      @@GrigoryRechistov What if you make it do recursion based on a rng value. Surely a compiler couldn't unroll this.

    • @Tawnos_
      @Tawnos_ Před 6 měsíci +11

      @@GrigoryRechistov I would see that as the continuing part of the interview, where you discuss that this makes assumptions about compiling without optimizations, why those assumptions are necessary (your comment about deterministic optimization), and the depth of testing required/importance of getting the right answer to the problem every time versus the vast majority of the time. All of these engineering tradeoffs are important outside of the toy problem.

  • @Double-Negative
    @Double-Negative Před 6 měsíci +272

    I think the result you have there is still not quite enough. The compiler could try to reason about the function call and realize that since it'll always terminate within 2 recursive steps, that it can be inlined into itself, then this inlined version of the function can reorder the addresses of its local variables. The solution to this depends on the compiler. On gcc, you can set -O0 to prevent inlining entirely, or you could put __attribute__ ((noinline)) into the function signature to make sure it's actually allocating stack space.

    • @shadamethyst1258
      @shadamethyst1258 Před 6 měsíci +17

      You could also mark other as volatile, so the compiler cannot make that assumption anymore

    • @EnterOne100
      @EnterOne100 Před 6 měsíci +1

      It can. Inline, the reorder other allocations arbitrarily. The just make sure to perform the operations one after the other.

    • @shadamethyst1258
      @shadamethyst1258 Před 6 měsíci +9

      @@EnterOne100 How can it inline, since the compiler no longer can prove that the recursion is finite? For all it knows, the volatile pointer could be set back to NULL between each recursive calls

    • @ciano5475
      @ciano5475 Před 6 měsíci +2

      Maybe we can check the position of the return address with __builtin_frame_address(0)

    • @coolcax99
      @coolcax99 Před 6 měsíci

      @@shadamethyst1258 or you can take the variables from input

  • @mrphlip
    @mrphlip Před 6 měsíci +30

    Comparing pointers that point to things that aren't part of the same object (ie two entries in the same array, or two members of the same struct) is undefined behaviour. The C compiler can make this code do whatever it wants.
    The spec reads:
    When two pointers are compared, the result depends on the relative locations in the address space
    of the objects pointed to. If two pointers to object types both point to the same object, or both point
    one past the last element of the same array object, they compare equal. If the objects pointed to
    are members of the same aggregate object, pointers to structure members declared later compare
    greater than pointers to members declared earlier in the structure, and pointers to array elements
    with larger subscript values compare greater than pointers to elements of the same array with lower
    subscript values. All pointers to members of the same union object compare equal. If the expression
    P points to an element of an array object and the expression Q points to the last element of the same
    array object, the pointer expression Q+1 compares greater than P. In all other cases, the behavior is
    undefined.

    • @shroomer3867
      @shroomer3867 Před 4 měsíci +1

      I practiced this myself after seeing that error being pointed out in the comments. It doesn't even require recursion.
      1) Declare a function where you will have a volatile two size array, this is done in order to invoke everything on the stack in C. Place two volatile int values with them inside of it.
      2) Since you declared the array you know which one goes above and below so you can just compare the two locations of the array (&array[0] > &array[1]) which would be defined behaviour.
      3) Do the return done in the video based on the difference of the two addresses and you're golden.

    • @KingBobXVI
      @KingBobXVI Před 2 měsíci +1

      ​@@shroomer3867 - this was my first thought but with structs... but actually I don't think it would work. Since the elements always have to go in ascending order, the function would always return "up", regardless of whether the stack grows up or down.
      The stack growing down doesn't mean the actual data goes backwards (or changes endianness). This would only affect whether the head or tail of the array is closer to the base of the stack.

  • @iTakethingsapart
    @iTakethingsapart Před 6 měsíci +187

    I'm not confident that after optimizations, there's any defined behavior that accurately describes the stack direction, especially since the stack is usually just an implementation detail so the compiler is free to not use one at all.

    • @robonator2945
      @robonator2945 Před 6 měsíci +27

      yeah that's one thing I'm not sure about here either. My first thought was to use a static array and compare &array[0] against &array[1] but the more I think about it a compiler could just sorta decide to work backwards for arrays compared to everything else, and it could decide to do *_anything_* backwards compared to everything else, so I'm not sure there is any architecturally-constant answer here.

    • @Stormrage476
      @Stormrage476 Před 6 měsíci +12

      You can always write inline assembly where you push two values onto the stack and compare their addresses. That way, you only have to trust the compiler not to optimize your assembly (which I assume it wont do), and the assembler to correctly assemble your code.
      You can also do it in only assembly as well.

    • @kodicraft
      @kodicraft Před 6 měsíci +42

      @@Stormrage476 At that point, if you know which CPU architecture you're targeting, you might as well just check out the documentation and write code expecting the stack to behave in the way it's documented. In fact, I'd argue the most practical solution to this problem is just having a check at compile-time of the target architecture and compare it to a known database since that comes with no runtime overhead at all for what's effectively a compile-time constant.

    • @VivekYadav-ds8oz
      @VivekYadav-ds8oz Před 6 měsíci +12

      @@robonator2945 Pretty sure arrays are laid out from lower to higher address, simply because the way to access them uses this assumption. C has no way of knowing to "pretend" when *(arr + i) should actually be *(arr - i) in cases of arrays because arrays frequently decay as pointers. Hence it MUST allocate them in the typical fashion.

    • @TranscendentBen
      @TranscendentBen Před 6 měsíci +4

      Oh, that sounds like a nightmare. You're assuming a particular target processor, or you're writing assembly for every possible processor and using lines like #ifdef PROCESSOR_6502 to assemble the right code for the target processor. And my understanding of the problem was to do it "in C."

  • @Delfigamer1
    @Delfigamer1 Před 5 měsíci +15

    1. "int x, y = 0" only initializes 'y'. To initialize both variables, you have to write "int x=0, y=0".
    2. 'Volatile' does not just mean "don't optimize", its semantics are actually quite narrow - writes and reads from volatile objects are considered as observable side effects, so the compiler has to preserve their order among themselves and relative to other I/O. Note that only actual reads and writes matter - if you allocate a 'volatile' variable, but never actually access its contents, then the compiler is allowed to remove it in its entirety. So, in your example, the 'volatile' qualifier only affects the initialization "y = 0", while 'volatile' on 'x' is literally useless.
    Also, as other comments noted, even the recursive function is still not safe. Firstly, a modern compiler is able to inline even recursive functions (so that you'd have many layers spelled out at once), which would bring both locals into the same scope and allow their reordering. Secondly, even without that - since all inputs to the function are known at compile time, the compiler may just interpret it right away on its own virtual machine, which might be completely different to the host, including the stack growth direction (this is also one of the reasons why you can't do reinterpret_cast in constexpr). So, to make sure your code actually gets run on the host, and the way you wrote it, you should also disable compiler optimizations.

  • @wChris_
    @wChris_ Před 6 měsíci +127

    The recusive version wouldnt work either, since the compiler could perform tail call optimization.

    • @almightyhydra
      @almightyhydra Před 6 měsíci +33

      Right, far simpler to use two functions and just disable compiler optimisations.

    • @CallousCoder
      @CallousCoder Před 6 měsíci +9

      @@almightyhydra even then you make the assumption that the function implementation uses a stack. C does not define that. So C would need to find a way to implement this without a stack when a CPU doesn't have a stack register or the running system doesn't have that concept.

    • @martinzihlmann822
      @martinzihlmann822 Před 6 měsíci +2

      and far more explicit, but i understand, it's not good content for a video.

    • @reductor_
      @reductor_ Před 6 měsíci +8

      the compiler cannot tail call optimize as the lifetime of the argument still needs to be maintained but it could inline it, in the end the code is bad an has undefined behavior you cannot compare two unrelated pointers.

    •  Před 5 měsíci

      Only, if it’s actually a tail call.

  • @obiwanjacobi
    @obiwanjacobi Před 6 měsíci +32

    In the 90's I wrote a function that could tell if a pointer was stack allocated but I solved it with inline assembly - just getting the SP register and comparing it to passed in pointer.

    • @IamusTheFox
      @IamusTheFox Před 4 měsíci

      I actually really like that solution.

  • @denischen8196
    @denischen8196 Před 6 měsíci +72

    You can use inline assembly to see if ESP increases or decreases when you use the push instruction.

    • @aspuzling
      @aspuzling Před 6 měsíci +53

      I think this is the only correct answer. In other words, "No I can't write a C program to determine the underlying architecture but I can write one in assembly".

    • @Double-Negative
      @Double-Negative Před 6 měsíci +47

      you also need to already know the architecture to write assembly

    • @adamsfusion
      @adamsfusion Před 6 měsíci +26

      That's putting the cart before the horse. In order to know what your stack pointer is to even reference it, you'd have to know the architecture.

    • @aspuzling
      @aspuzling Před 6 měsíci

      Exactly

    • @gregorymorse8423
      @gregorymorse8423 Před 6 měsíci +7

      Lol if you know the machine language, you could also go to the Intel manual for x86 and see push eax pseudo code of [ESP]

  • @samsthomas
    @samsthomas Před 6 měsíci +1

    Love your videos. I like to think I’m a semi-competent C hacker on a good day, but it’s amazing how much I’ve forgotten or don’t consciously think about regularly since college.

  • @Gregorius421
    @Gregorius421 Před 5 měsíci +8

    2:50 the return is inverted. The compiler is also doing the opposite of what you expect it to do: `-O2` and `-O3` optimization reverses the order of the local variables.
    Thus the result is double wrong, which in boolean logic ends up being right 😄
    Also, simply `return &x < &y` will do. `return true; else return false;` is also a telling sign for the interviewer.

  • @FinBoyXD
    @FinBoyXD Před 6 měsíci +8

    Ok, I don't exactly understand how the stack is pictured here. So in the first example, if the address of y is greater than of x, then in this example it's growing down. But if the y is allocated later, then why does this mean then it's growing down (ie more negative) if the latter variable address was higher?
    But if you take the recursive example it makes sense, since x is now the variable that's allocated later, because we are at the end of the recursion and "other" is the previously allocated variable (and it's now right side of the greater than sign). So this makes sense to me, and seemingly disagrees with the first example. Yet their print was the same.

    • @Wail0s
      @Wail0s Před 3 měsíci

      This is the comment i was searching, in fact , if we suppose that x is pushed into the stack before y , then &x is greater than &y , therefore upordown() should return true.

  • @rogerdeutsch5883
    @rogerdeutsch5883 Před 5 měsíci

    Fascinating question I had not thought about before. Fantastic & concise explanation with great code example. Subscribed!

  • @ArielNMz
    @ArielNMz Před 5 měsíci +1

    Very cool stuff. I haven't touched C in about 10 years since I moved on to Python/JS/Web and I'm glad to know I could've gotten almost 80% of that in an actual interview, assuming I knew it was a C interview and had time to do a quick refresher, I owe it all to my CS101 teacher back in high school.

  • @ghostphreek7301
    @ghostphreek7301 Před 6 měsíci +20

    I am a bit confused on the first approach? if the pointer to x is greater then y then wouldn't that mean the stack is growing down and getting more negative? Since the second value that was allocated on the stack, y, has a smaller address then x which was the first value allocated on the stack? Assuming no compiler optimizations are made.

    • @AI-vy6ky
      @AI-vy6ky Před 5 měsíci +3

      I thought the same thing. Am I missing something obvious?

    • @00jknight
      @00jknight Před 5 měsíci +3

      Agreed. I'm pretty sure his first example is not only incorrect, but he misspoke multiple times when talking about it.

    • @noomade
      @noomade Před 5 měsíci +3

      @@00jknight LOL, I thought I was going mad... now i am totally confused because of course you could be the one that is wrong since you agree with a noob like me :P

    • @Gregorius421
      @Gregorius421 Před 5 měsíci +3

      You're right. The condition is mistakenly inverted, that is wrong. So why is the result as expected? The compiler allocated the variables in a different order than defined, or did some other optimization that fooled the logic. This is the danger of undefined behavior. Funny that the mistake masks the compiler's unexpected behavior, without the presenter's knowledge.
      In any case the challenge achieved its goal, we now know quite some about him: did not verify the logic (no testing, even mentally), wrote if () return true; else return false; instead of return (); Rookie mistakes.

    • @noomade
      @noomade Před 5 měsíci

      @@Gregorius421 can you write a correct solution (as in one that you would write if you were FORCED to write SOMETHING in an interview)

  • @vytah
    @vytah Před 6 měsíci +9

    Both solutions invoke undefined behavior: you are not allowed to compare unrelated pointers for which is greater, only for equality. A compiler is free to optimize those comparisons to an arbitrary constant, irrespective of the stack growing direction.

    • @erikkonstas
      @erikkonstas Před 6 měsíci +1

      LOL, we're already within UB territory with that problem statement alone (which assumes the existence of a stack), so...

    • @vytah
      @vytah Před 6 měsíci +1

      @@erikkonstas Yes, but knowing it is undefined behavior and not merely implementation-defined behavior means we cannot rely on the solution even if we know there is a stack and there is nothing complicated with pointer representation, as the compiler is free to do whatever. Someone commented under the video that the result depends on optimization settings on x86_64, one of the better behaved targets. Someone else in the comments posted a better solution which, while still technically invoking undefined behavior, at least survives optimizations, as it uses volatile function pointers, and it would work reliably under a few reasonably common conditions.

    • @erikkonstas
      @erikkonstas Před 6 měsíci +3

      @@vytah Yeah I agree, I'm saying the interviewer is asking a bit of an iffy question here.

    • @noomade
      @noomade Před 5 měsíci

      @@vytah where is that better solution?

    • @vytah
      @vytah Před 5 měsíci

      @@noomade Here is it, by @dekutree64
      volatile int *ap, *bp;
      bool f1(){*ap=*bp=1; return bp>ap;}
      bool(*volatile fp1)()=f1;
      bool f2(){int b=0; bp=&b; return (*fp1)();}
      bool(*volatile fp2)()=f2;
      bool up(){int a=0; ap=&a; return (*fp2)();}
      The conditions I think this relies on are 1. the stack exists 2. both return addresses and local variables are stored on that stack 3. all pointers are comparable 4. for two separate allocations, either all pointers into one are less than all pointers into the other, or greater 5. C implements pointer comparison the simplest way 6. sizeof(size_t) == sizeof(uintptr_t) 7. compiler always respects volatile for globals. Maybe few more.

  • @steveaguay
    @steveaguay Před 3 měsíci

    The second you said recursion it all made sense. Super cool question.

  • @ProjectKneepads
    @ProjectKneepads Před 6 měsíci

    The recursive solution is very clever! I hadn't considered compiler optimization. Thanks for sharing!

  • @williamsloan7857
    @williamsloan7857 Před 6 měsíci +14

    A few questions about your final solution:
    1. Shouldn’t x still still be volatile?
    2. Are you worried about if the compiler decides to do some form of optimization and removes the recursive call?
    3. Why not get the stack pointer and compare it to the previous stack pointer?

    • @vytah
      @vytah Před 6 měsíci +7

      You cannot get the stack pointer (or anything resembling it) in C. You need to drop to assembly, which means you already know how your stack works.

    • @shipweck6253
      @shipweck6253 Před 5 měsíci

      @@vytah you can write inline assembly in C. Though this kinda defeats the purpose of writing it in C.

    • @solhsa
      @solhsa Před 5 měsíci

      1) doesn't matter, the solution is still wrong. 2) and it's wrong because of this. 3) there's no stack pointer (or requirement for existence of stack) in C

    • @valizeth4073
      @valizeth4073 Před 5 měsíci

      @@shipweck6253 Not really given that the task is "impossible" in C. If the interviewer asked me such a question I would assume it's a trick question.

  • @zaper2904
    @zaper2904 Před 6 měsíci +80

    I personally would allocate a small array to force the compiler to allocate on the stack.

    • @Pi7on
      @Pi7on Před 6 měsíci +8

      Yeah that's seems like the simplest way to me too.

    • @aspuzling
      @aspuzling Před 6 měsíci +7

      Could you explain a bit more? Why is allocating an array different from allocating an int? Why does allocating a small array guarantee that it will be allocated on the stack? What do you mean by "small" exactly? Is there a threshold that means larger arrays get allocated to the heap instead and if so what is that threshold?

    • @zaper2904
      @zaper2904 Před 6 měsíci +25

      @@aspuzling You can't store an array in registers and an array in C must be allocated sequentially in a continuous chunk of memory otherwise pointer arithmetic doesn't work.
      Small means just that I'd personally allocate maybe 100 numbers just in case but as far as I know even just 2 would be fine.

    • @ryanmcclue6867
      @ryanmcclue6867 Před 6 měsíci +8

      @@aspuzling An int would be 32bits on 64bit machine. So, the compiler could easily put this in any available 64bit general purpose register. If you were to allocate an array of size say 8 ints 'int arr[8];' then it's not feasible to use registers to store. Regardless of size, would only go on heap if storing to an address returned with 'malloc()' and friends.

    • @myusernameislongerth
      @myusernameislongerth Před 6 měsíci

      ​@@zaper2904if you take an address of an object, it cannot be in register only.

  • @Zeutomehr
    @Zeutomehr Před 6 měsíci

    my favourite video of yours in a while, really liked this one

  • @sandeepvk
    @sandeepvk Před 4 měsíci

    I loved this solution. Thanks. I am new to C but your channel has helped me step up by several orders of magnitude. You make the topic very interesting.

  • @Peter_Schluss-Mit-Lustig
    @Peter_Schluss-Mit-Lustig Před 6 měsíci +1212

    it kinda hurts to see you return a boolean through an if-statement

    • @LowLevelLearning
      @LowLevelLearning  Před 6 měsíci +495

      I was explicit for the sake of the video but I understand what you’re saying :P

    • @69k_gold
      @69k_gold Před 6 měsíci +39

      You mean the non-ternary approach?

    • @arjuntt2604
      @arjuntt2604 Před 6 měsíci +204

      Easily understandable code does add value

    • @spacey6960
      @spacey6960 Před 6 měsíci +13

      @@burndly But would that code change to not use the if else? Wouldnt you need the ? : operator?

    • @batatanna
      @batatanna Před 6 měsíci

      Nop. You can just return the comparison. ​@@spacey6960

  • @m.hosseinmahmoodi
    @m.hosseinmahmoodi Před 5 měsíci +8

    I actually thought of an extra function that has a variable and returns the address of the variable to UpOrDown and UpOrDown compares it to the address of its own variable. Recursion removes the need to create an extra function but adds the need for a pointer to be passed instead.

    • @BittermanAndy
      @BittermanAndy Před 5 měsíci +1

      Yeah, recursion unnecessarily confuses this IMO.

    • @maexxx
      @maexxx Před 5 měsíci +1

      That's exactly how I approached the question:
      typedef volatile int stackvar_t;
      int comparestackpositions(stackvar_t *otherstackpointer) {
      stackvar_t thisstackvariable;
      return &thisstackvariable - otherstackpointer;
      }
      bool upordown(void) {
      stackvar_t stackvariable;
      if (comparestackpositions(&stackvariable) > 0)
      return true;
      return false;
      }

  • @Little-bird-told-me
    @Little-bird-told-me Před 22 dny

    i keep revisiting this video to get my head cleared up on what heap and stack actually mean.

  • @andrematheus7527
    @andrematheus7527 Před 5 měsíci +1

    i didnt understand a single thing you have said through this entire video. loved it 👍

  • @crimsonmentone6527
    @crimsonmentone6527 Před 6 měsíci +11

    Isn't the logic of the first upordown function wrong? If x adress is greater than y address then the stack is decreasing

  • @MenkoDany
    @MenkoDany Před 6 měsíci +9

    Wow, thanks for the video! The first implementation is the one that I had in my head. The only twist I could think of is that the function could be inlined, but that shouldn't affect anything.

  • @BradCypert
    @BradCypert Před 4 měsíci

    I write almost no C or C++ and this was super fascinating. One of my favorite tech videos in a long, long time. Thank you helping me continually learn every day!

  • @rogerparrett3242
    @rogerparrett3242 Před 6 měsíci +2

    I'd just write a few lines of c, compile with the S option, open gdb, and look at the assembly code. Then, the interviewer would know I knew C, assembly, and how to use a debugger other than printf().😂😂

  • @tonysofla
    @tonysofla Před 6 měsíci +51

    Most embedded C compilers would use Registers, not the Stack for temporary variables.
    Recursive probably would force it to use the stack as the compiler don't know how much space it needs.
    Side note, A Contant Int would force it to store it as ram variable, if you want it to be remembered for next call.

    • @anar.bastanov
      @anar.bastanov Před 6 měsíci +22

      Recursion doesn't necessarily force variables to be allocated on the stack, taking their address with & operator does (because values on registers don't have addresses)

    • @sorek__
      @sorek__ Před 6 měsíci +1

      Wouldn't constant be most likely kept in flash though? Especially for some embedded architectures.
      Another way to make sure would be to create compile size array to go around registers being used. But I think volatile would stop it from happening and also calling for address of variable wouldn't make it use registers (I think).

    • @tonysofla
      @tonysofla Před 6 měsíci

      @@anar.bastanov As return address is always store on the stack, would the test allow inline assemble? Have to make sure to use noinline for the function, and you would need to use correct asm for the platform.
      uint32_t sp;
      asm( "mov %%rsp, %0" : "=rm" ( sp ));
      return sp;

    • @tonysofla
      @tonysofla Před 6 měsíci

      @@sorek__ Using the keyword Constant inside a function is different. Passing an array of like 20 vars, should force the use of the stack. And that is why passing a single pointer is betters, so you don't use 20 stack push/pop's

    • @natnial1
      @natnial1 Před 6 měsíci

      You can just use a print statement to guarantee the use of a variable’s address cannot be optimized away, this way you get an actual automatic variable rather than a register.

  • @DesyncX
    @DesyncX Před 6 měsíci +9

    I was thinking in the same direction of multiple function stack frames.
    I was thinking of 2 functions:
    1 basic upordown() function with no parameters which returns enum UP or DOWN. This upordown would simply call with the address of a variable in upordown another function checkIfStackIncreasesUp(int *reference_address) which returns bool by comparing the parameter received address with some internal variable's address. No recursion is needed :)

    • @Leto2ndAtreides
      @Leto2ndAtreides Před 6 měsíci +4

      Probably better to not name it upordown because it's returning a bool. I'm legit inclined to name it doesStackGrowUp... And I've never used does in a function name before. lol
      Your teammates should ideally not have to read the code of the function to understand what it's doing.
      And comments are not an adequate substitute for good naming.

    • @Gregorius421
      @Gregorius421 Před 5 měsíci

      Use `___attribute___ ((noinline))` to avoid inlining. No need for recursion. Alternatively `clang -O0` disables optimizations like inlining.

    • @Gregorius421
      @Gregorius421 Před 5 měsíci

      @@Leto2ndAtreides Yeah, the name was not really catchy. I thought about isUpOrNo() would get more interest.

  • @bannanafruitsalad
    @bannanafruitsalad Před 4 měsíci

    It's funny that my initial solutioun was to use recursion and I had a total "duhdoy" moment when you mentioned just initializing two variables lol

  • @rosettaroberts8053
    @rosettaroberts8053 Před 6 měsíci +2

    If you look at the C11 reference for relational operators, section 6.5.8, this pointer comparison invokes undefined behavior since the variables aren't part of the same array or struct. So, a compiler would be allowed to optimize your function to lie about which way the stack grows. I'm not sure if any compilers actually do optimize this comparison.

  • @blazingblast
    @blazingblast Před 6 měsíci +15

    Some compiler options optimize out using multiple stack frames for recursion, if possible

    • @ic6406
      @ic6406 Před 6 měsíci

      Probably the compiler doesn't have rights to optimize this case because recursive call depends on values below the call

    • @OMGclueless
      @OMGclueless Před 6 měsíci

      ​@@ic6406 There's no dependency on values below the call. `return upordown(&x)` can be easily tail-call-optimized into a simple jump because there is no additional work to do after the call returns and the return value of the callee is our return value.

  • @baranjan6969
    @baranjan6969 Před 6 měsíci +4

    I love it! I would try to use inline asm and compare esp but recursion is honestly much better

    • @gregorymorse8423
      @gregorymorse8423 Před 6 měsíci +3

      alloca is much better and uses no function calls

  • @henriksundt7148
    @henriksundt7148 Před 6 měsíci +2

    2:17 Security people will comment that int x, y = 0; only initializes y with 0, not x. It should be int x = 0, y = 0; One more issue is that with comma separated variable initialization, C does not specify the order that the initialization has to take place. Thus, to check the stack direction, it should be int x; int y;

    • @noomade
      @noomade Před 5 měsíci

      still no guarantee that x is initialized first though right??

    • @henriksundt7148
      @henriksundt7148 Před 5 měsíci

      @@noomade Even though in practice, most compilers initialize left-right, it is not defined as part of the C standard. E.g. if you do int x=0, y=x; you get a compiler error.

    • @henriksundt7148
      @henriksundt7148 Před 5 měsíci

      @@noomade Statements separated by semicolon are executed sequentially, thus int x=0; int y=0; is guaranteed to initialize x first. I think you are right that only declaring them by int x; int y; without initialization does not impose a specific order on the stack, it may be up to the compiler.

    • @noomade
      @noomade Před 5 měsíci

      ​@@henriksundt7148 Hmm. I am sceptical that you can guarantee that the compiler will not do something funny with the ordering of your code when it optimizes it.
      It would seem that even WITH initialization you cannot guarantee the order of your code... at least according to some c experts on here that refer to the docs etc.

    • @henriksundt7148
      @henriksundt7148 Před 5 měsíci

      @@noomade There may even not be a stack, as other commenters here also discuss :) But if you assume there is a stack, and check the addresses right after a proper initialisation, I think you can be quite sure about the direction.

  • @TheMasonX23
    @TheMasonX23 Před 6 měsíci

    Yay, I'm a non-C programmer who not only knew the first naive approach, but thought about how the compiler could possibly optimize it and ruin things. Didn't quite figure out a solution before I hit play, but I'm still just happy I was on the right track :)

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +2

      There is no solution in portable C, because the C standard does not have "stack".

  • @MeRezMo
    @MeRezMo Před 6 měsíci +9

    Haha, fun stuff. This was my one of my interview questions at Microsoft when I tried to intern there. I had limited to no knowledge about OS architecture. It wasn't even the position I was going for. Anyway, I failed it.
    My initial answer was create 2 variables and compare the address as you've said but it wasn't enough as they can be randomly allocated or as you said optimized.
    Create 2 functions and creating a variable inside each would be good as they get a full row of memory.
    Also, it can avoid any complexion like recursion.
    Still this question hunts me to this date. haha was over 6 years ago. Luck is a big factor in life. Knowledge is what brings that luck closer to us !~
    Keep up the good stuff, interesting videos.

    • @thomashabetsse
      @thomashabetsse Před 5 měsíci +1

      It's not just that they may be reordered. It's also that comparing pointers not part of the same array is undefined behavior.
      You could get back anything. You could get back that x is above y AND y is above x, because with undefined behavior logic does not apply.

  • @sprytnychomik
    @sprytnychomik Před 6 měsíci +42

    Plot twist: there is no concept of stack (nor heap) in C standard.

    • @CallousCoder
      @CallousCoder Před 6 měsíci +6

      Indeed it's an operating system and/or hardware implementation. The compiler conforms to that.

    • @MrEdrftgyuji
      @MrEdrftgyuji Před 6 měsíci +9

      There is also no concept of memory addresses in C. The numerical value of a pointer could be anything, as long as it can be dereferenced back to the original variable.
      Pointer arithmetic and comparison operations are guaranteed to work, though.

    • @SimonClarkstone
      @SimonClarkstone Před 5 měsíci +3

      ​@@MrEdrftgyuji Pointer arithmetic and comparisons are not always guaranteed to wwork. As a rule of thumb, if you compare two pointers that aren't identical and don't point within the same array, the result is undefined behaviour.

  • @uuuummm9
    @uuuummm9 Před 5 měsíci +1

    I have a strong feeling that in the situation where i would need to know the direction of the stack there should be much better and reliable way to do it.

  • @weinihao3632
    @weinihao3632 Před 6 měsíci +2

    int main (int a, char *b[]) { puts (b > *b ? "UP" : "DOWN"); return 0; }
    or, if system is freestanding:
    void main (void) { char *a = alloca (1+(int) &main % 5), *b = alloca (1+(int) &main % 5); puts (a < b ? "UP" : "DOWN"); }

  • @d4y92
    @d4y92 Před 6 měsíci +51

    I'd probably solve that problem using "alloca()", which allocates memory in the stack and return its as a pointer

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +9

      A single call to alloca() is not enough. It will return you a pointer to (allegedly) data on stack, yes. But then you need to compare it to another pointer allocated on the same stack. And this is where no guarantees are provided by the language about the order in which multiple alloca()'s or any other local allocations will happen.

    • @Henrix1998
      @Henrix1998 Před 6 měsíci +8

      ​​​@@GrigoryRechistovmaking second alloca conditional based on the first would guarantee the order
      int *first, *second;
      first=alloca(1);
      if (first) second=alloca(1);
      return first>second;

    • @rb1471
      @rb1471 Před 6 měsíci

      I think it'd be slow in comparison

    • @Catterjeeo
      @Catterjeeo Před 6 měsíci

      I doubt the speed is all that much of a factor here. @@rb1471

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +2

      @@Henrix1998 alloca() never returns NULL, so in this case the conditional may be optimized away as always true. Then the allocations may be reversed.

  • @potatoguy413
    @potatoguy413 Před 6 měsíci +31

    My first thought was to create a struct with two variables, initialize an instance and compare the pointers of the variables, as structs are required to keep their order.

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +23

      Yes but that will answer about how structs are stored in memory, not how stack allocations are made. There is a difference, and the orders may be different.

    • @matthewrossee
      @matthewrossee Před 6 měsíci

      @@GrigoryRechistov How the order can be different?

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +1

      @@matthewrossee Easily. The compiler is allowed to put them in any order or optimize them away. "volatile" will help with the latter but not with the former.

    • @matthewrossee
      @matthewrossee Před 6 měsíci

      @@GrigoryRechistov ok that makes sense. What do you mean by optimizing them? From what I understand, the compiler can’t inline the values because then how could you take the address of these variables?

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +1

      @@matthewrossee It cannot inline values of variables but it can values of addresses of variables inline (or make other conclusions like being NULL or non-NULL). Note that the code in the example does not dereference pointers in question, it only operates on addresses themselves.

  • @highdefinist9697
    @highdefinist9697 Před 6 měsíci

    Yes, this an interesting one, because (other than knowing what a stack is), the main issue is really the "fighting" against compiler optimizations, hence knowing about "volatile" etc... becomes important.

  • @richardslater677
    @richardslater677 Před 5 měsíci

    I didn’t understand the question so I listened to the video and I can categorically state that I now understand it even less.

  • @groogoloog
    @groogoloog Před 6 měsíci +11

    I think your second/recursive solution can be incorrect due to tail recursion optimizations (reusing the same stack frame when recursing!)
    That being said, I’m no expert, so I may be wrong

    • @natnew32
      @natnew32 Před 6 měsíci +1

      The issue is, an address to a local variable was passed to the called function- destroying the stack would also destroy the reference.

    • @gabiold
      @gabiold Před 5 měsíci +1

      The reference itself is not destroyed, just the memory it points to is no longer "reserved". However, pleaee note that the code does not use a reference to a destroyed variable. The second recursive run determines the result, where the first instance of the variable (whose address is in the argument) is still exists. The second run returns the boolean outcome, not the address of its local variable which is going to be destroyed. So theoretically it is not an use-after-free error.
      Regardless of this, while the question itself is interesting, there is no way to write this program with 100% certainity that it will work in a defined way on all architectures.

  • @alexandratsankova5825
    @alexandratsankova5825 Před 6 měsíci +3

    My first idea wad using functions and checking a variable in their scope against eachother, however i didn't think about using recursion and i guess using recursion also eliminates (?) The possibility of the compiler inlining the function and breaking that logic

    • @clawsie5543
      @clawsie5543 Před 6 měsíci +4

      On my machine, I get a different result with -O3 because gcc removes recursive call, so...

  • @hermand
    @hermand Před 5 měsíci

    I jumped to recursion as my approach when you first posed the question so I'm putting C on my CV now!

    • @noomade
      @noomade Před 5 měsíci

      but the C experts in the comments are saying that the recursive approach offers no guarantee either... and that even those that have gone a step further and used alloca() are also wrong :(
      Languages: -C programming language-

  • @DexieTheSheep
    @DexieTheSheep Před 6 měsíci

    what's the music used? i heard it on kitboga before and it sounds like smt by prod. riddiman not sure

  • @khatdubell
    @khatdubell Před 6 měsíci +8

    You didn't need to write a recursive function like that.
    Since all you care about are addresses, and you have no intention of dereferencing them, you can take the address of a variable on the stack in one function, and return that, as a pointer, to the calling function.
    Then compare that address to the address of a variable in the current function.

    • @MenaceInc
      @MenaceInc Před 6 měsíci +1

      Cursed but I kinda like it

    • @sanderbos4243
      @sanderbos4243 Před 6 měsíci +1

      This is the initial version I wrote, thanks for confirming it's valid

    • @scragar
      @scragar Před 6 měsíci +2

      This is how I thought to solve it. Recusion works, but his function does two completely different behaviours based on arguments, which to me screams the need for a distinct separation of the methods, one to return the pointer for a variable on the stack, and another to call it and compare addresses.

  • @JDAndNightrain
    @JDAndNightrain Před 6 měsíci +8

    Good video. My only issue is with a boolean function being called upordown. It makes the code read as "Is the stack growing up or down? Yes/No." and does not imply directionality which is the real question we're asking. Something like isGrowingUp() or isGrowingDown() would be better.

  • @jasper265
    @jasper265 Před 5 měsíci

    I like this question a lot. I'm not a C developer, so my first idea was that it would be way out of reach. But then I realized that I could write some pseudo-code. I didn't realize I could just use the addresses of local variables, so my pseudo code used some mysterious method to get the stack pointer. But I was using two separate functions, similar to your recursion (but in my opinion, more appropriate here than recursion, for readability reasons).
    The amount of reasoning you can do about this even if you don't have the full answer shows how well this question works!

    • @noomade
      @noomade Před 5 měsíci

      The best answer on here is the one that explains why this cant be done with any guarantees ( by Grigor)

  • @AceofSpades5757
    @AceofSpades5757 Před 6 měsíci

    That was a great exercise. Thanks!

  • @ForsoArk
    @ForsoArk Před 6 měsíci +34

    I think i would ve used alloca() which allocates memory in the stack frame and compared the first and last element's address but your recursion based answer seems more platform proof.

    • @4zv4l38
      @4zv4l38 Před 6 měsíci +1

      Why alloca instead of an array ?

    • @dobacetr
      @dobacetr Před 6 měsíci +14

      @@4zv4l38I think you need to alloca() twice in sequence and compare their memories. If you use an array and compare its end and beginning, I think it should always show that last-first is positive (because that's the direction an array is oriented in).

    • @ForsoArk
      @ForsoArk Před 6 měsíci +3

      @@dobacetr you are right otherwise indexing wouldn't work. I didn't think of that thank you !

    • @ForsoArk
      @ForsoArk Před 6 měsíci +6

      @@4zv4l38 i might be wrong ( and probably am) but i feel like alloca would always allocate memory in the stack whereas variables and local arrays might be put into register without the volatile keyword also it's just another use case of alloca

    • @chri-k
      @chri-k Před 6 měsíci

      The man page i have on alloca can be summarised as "alloca does something, and probably returns valid and unused stack memory in the process"
      I would only trust alloca when you know everything about the system. The whole purpose of this code is that you don't, so i doubt this is reliable

  • @breadleaf6794
    @breadleaf6794 Před 6 měsíci +5

    Great video! However I did find something I found interesting when trying to compile both of these solutions... I compiled a program with both of your solutions using gcc 11.4.0 and clang 14.0.0, For your first solution gcc gives Down while clang gives Up... For your second solution both compilers return Down. Edit: I am using pop-os 6.5.6 if anyone else wants to verify this

  • @noomade
    @noomade Před 5 měsíci

    Can you try and force some sort of ordering if in your first implementation you do something like if (x) y = 1; ????

  • @senzmaki4890
    @senzmaki4890 Před 6 měsíci +1

    this doesnt make sense to me to, in the first one the stack would look sth like
    0 x
    +1 y
    in the second one wouldnt it be
    0 other
    +1 x
    since other comes first, so why are we saying x > other when we in the first one we initially said x > y, can someone explain it to me

  • @mariosshadow8533
    @mariosshadow8533 Před 6 měsíci +6

    I tried both local variables, volatile or otherwise, and with recursion, and they both had the same results. Normal compilations reported downward growth, but optimized compilations reported upward growth. Something to keep in mind.

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +5

      This means that the either program's result is unspecified or undefined. Compiler optimization level or compiler choice should not affect outcomes of well-defined C programs. Ergo, this program is not well-defined (see other messages in the comments from many commentators what assumptions are wrong)

    • @vytah
      @vytah Před 6 měsíci +3

      ​@@GrigoryRechistovit's the pointer comparison: comparing which of two unrelated pointers is greater is undefined behavior.

    • @GrigoryRechistov
      @GrigoryRechistov Před 6 měsíci +2

      @@vytah True. Only pointers that refer to insides of the same array or struct/union may be compared, otherwise the C standard (6.5.8p5) explicitly marks the result as undefined behavior.

    •  Před 5 měsíci +1

      @@GrigoryRechistovone past the container is also still fine.

    • @GrigoryRechistov
      @GrigoryRechistov Před 5 měsíci

      @ Agree. I always liked this "oh, we have this off-by one problem. All right, let's allow it everywhere"

  • @redcrafterlppa303
    @redcrafterlppa303 Před 6 měsíci +7

    You don't really need recursion there.
    You can make 1 function that returns the address of a volatile local variable as a usize_t (to not confuse people to dereference the invalid pointer/address)
    And then make the actual upordown function that compares the result of the other function to its volatile local variable.
    size_t nextStackframeAdrr() {
    volatile int x = 0;
    return (usize_t) &x;
    }
    bool upordown() {
    volatile int x = 0;
    volatile size_t addr = nextStackframeAddr();
    volatile size_t addr2 = (size_t) &x;
    return addr2 < addr;
    }

    • @pwii
      @pwii Před 6 měsíci +2

      does volatile enforce the order of operations too? from what I know, there is still nothing preventing the compiler from re-ordering things like variable declarations

    • @redcrafterlppa303
      @redcrafterlppa303 Před 6 měsíci

      @@pwii no. And I'm not sure if this code works korrekt in all cases. But it should work in all cases his version works.

    • @anon_y_mousse
      @anon_y_mousse Před 6 měsíci +3

      @@pwii Indeed. You have to turn off optimizations to get it to work, and if you do, you have no need for the volatile keyword anyway.

    • @var67
      @var67 Před 6 měsíci +4

      usize_t is not a predefined or library type. There is size_t which is unsigned, and ssize_t which is signed.

    • @redcrafterlppa303
      @redcrafterlppa303 Před 6 měsíci +1

      @@var67 thanks I wasn't sure which way around it was. I usually code in rust where it's size and usize

  • @crogersdev
    @crogersdev Před 5 měsíci

    Is that a plugin or video editing for the preview window? It's lovely, I'd love to add it to my vimrc

  • @househall
    @househall Před 6 měsíci

    One of my ideas was to check argument's address with local variable's address, there's huge chance that arguments pushes first before locals are allocated.

  • @TheyCallMeHacked
    @TheyCallMeHacked Před 6 měsíci +5

    Wouldn't that break if the compiler decides to do tail call optimisation?

    • @AnataBakka
      @AnataBakka Před 6 měsíci

      tail call is impossible in that case, or, rather, even if possible you should assume there is no tail call for that case, see david wragg c tail calls 2014

    • @natnial1
      @natnial1 Před 6 měsíci

      @@AnataBakkaso basically if the behavior is well defined without TCO but breaks if TCO is applied then it isn’t applied.
      This of course assumes the program is well defined which it would be as long as long as the pointers are casted before comparing them

    • @erikkonstas
      @erikkonstas Před 6 měsíci

      @@natnial1 Yeah, that "well defined" part is where it all breaks; TCO does not "break" anything in regards to well-defined behavior, but here we actually want to tame UB.

  • @vasishtabhat9280
    @vasishtabhat9280 Před 6 měsíci +7

    please make videos on volatile, __attribute__ and ## in C

    • @arta6183
      @arta6183 Před 6 měsíci

      those are completely different things. here is a short explanation of two of them:
      volatile basically tells the compiler to not optimize whatever you've marked as volatile. I recommend messing around with it in godbolt.
      ## is some sort of an operator used in the CPP (C Preprocessor) that joins two tokens together, so imagine this:
      #define JOIN(a, b) a##b
      then `JOIN(hello,World)` would produce the token `helloWorld`.

    • @vasishtabhat9280
      @vasishtabhat9280 Před 6 měsíci

      @@arta6183 thanks for info😀 .. i just wanted to know how those features work under the hood...So i suggested separate videos on them

  • @jeyaprakashrajaiah32
    @jeyaprakashrajaiah32 Před 5 měsíci

    i m using rp2040 pwm with dso 100mhz i want create freq 100mhz but the square wave isn't coming it has much noice can you tell me please whats the reason and is it possible to create high freq with pico

  • @qwertz12345654321
    @qwertz12345654321 Před 6 měsíci

    I had a program where I needed to do some pointer arithmethic and checked out the documentation for comparisons between pointers, It said that comparing two pointers that are not inside the same structure has no definied behaviour. So can we be sure, that the stack always goes in one direction?

  • @robertcorbin2749
    @robertcorbin2749 Před 6 měsíci +3

    You mention that some architectures have the stack grow +/- differently. Why is this? Why wouldn’t the architectures be consistent with this concept?

    • @minirop
      @minirop Před 6 měsíci +2

      Because they can? architectures are imcompatible with each other, why would they follow the same rules?

    • @robertcorbin2749
      @robertcorbin2749 Před 6 měsíci

      @@minirop good point. Just curious. I am sure there are some things different architectures can’t do differently. I guess I am wondering why the his isn’t one of them.

    • @Nezuji
      @Nezuji Před 6 měsíci +1

      Cars can drive on either side of the road, but it doesn't matter WHICH side as long as everyone agrees. In a similar way, a computing architecture can grow its stacks "up" or "down", as long as it sticks with one or the other. Stack growth is invisible to 99% of programmers anyway.

  • @CSalvarani3
    @CSalvarani3 Před 6 měsíci +8

    The use of recursion is unncessarily complicated. You only ever recurse one layer deep - just use a helper function.

    • @1vader
      @1vader Před 6 měsíci +2

      Yeah, it's also confusing and makes the API worse since you have to pass in NULL to the call for no reason.

    • @dennismuller1141
      @dennismuller1141 Před 6 měsíci +1

      I think he did it to prevent a compiler optimization that just moves the function body of the helper function to its only call, therefore creating a similar situation as before, where the compiler can change the order of the variable declarations.

    • @1vader
      @1vader Před 6 měsíci +1

      @@dennismuller1141 Hm, that kinda makes sense. Though I also wouldn't be surprised if the compiler could optimize the recursive call the same way. Tbh in general, I'm not sure there's anything stoping the compiler from optimizing this whole concept into whichever direction it wants, since stack direction doesn't seem to be something that's guaranteed by the spec.

    • @CSalvarani3
      @CSalvarani3 Před 6 měsíci

      @@dennismuller1141 You make a fair point. Compilers are free to inline functions like you describe.
      But I don't think the recursive solution is exempt from this. Compilers are pretty smart these days. They are free to "unroll" recursive calls as well as optimize away unreachable branches. So in the end, the same problem arises.
      The better approach would be to use some compiler annotation, such ass GCC's "noinline" annotation, to prevent such a compiler optimization.

  • @user-vm7eq5vc9s
    @user-vm7eq5vc9s Před 6 měsíci

    Why is there link to your merch store, but when i click on that link it says that the store is closed?

  • @wtfdoiputhere
    @wtfdoiputhere Před 6 měsíci

    Do you have any low level programming books to recommend? Fyi i have a bachelor's degree in computer engineering so I'm not 100% new but I'd still love a more "structured" manner of learning

  • @drkspace
    @drkspace Před 6 měsíci +3

    You could also just do
    volatile int x =0;
    int y=x+1;
    [rest of the first function]
    The optimizer cannot change the order since y is dependent on x and x might change between the lines.

  • @bbsushi8871
    @bbsushi8871 Před 6 měsíci +3

    Can't you also compare the address of main with the address of the upordown function?

    • @martandrmc
      @martandrmc Před 6 měsíci +2

      You can't because the functions' code doesn't live in the stack but in the exact opposite side of memory (at least in x86), in the text segment. Only local variables and some other internal values (return address, last frame pointer) get placed in the stack.

    • @xarkenz
      @xarkenz Před 6 měsíci

      I don't think that'd work, because the addresses of functions don't point to the stack at all.

    • @Elesario
      @Elesario Před 6 měsíci

      The address of the functions won't be in any guaranteed order, but presuming you mean the address of a variable in main vs one in the upordown function, I believe he was trying to avoid the case where the compiler optimises out the call being made and therefore making the comparison not work as intended.

    • @xarkenz
      @xarkenz Před 6 měsíci

      @@Elesario Oh, that would make more sense. That's actually a very good idea then.

  • @arjix8738
    @arjix8738 Před 6 měsíci +2

    at the start you said "job interview" but it sounded too similar to "java interview" so I mishead it
    and then you mentioned C, and I was like wtf

  • @SirLightfire
    @SirLightfire Před 6 měsíci +1

    This is exactly my thought process when you asked the question
    - compare two variables addresses off the stack
    - but what if the compiler mucks with their order?
    - move the variables into function arguments,
    - but what if the compuler reverses function arguments?
    - use a function call to force a call frame onto the stack and compare addresses across the call frame

    • @gregorymorse8423
      @gregorymorse8423 Před 6 měsíci

      alloca...easy solution

    • @noomade
      @noomade Před 5 měsíci

      @@gregorymorse8423 but also wrong apparently!

    • @gregorymorse8423
      @gregorymorse8423 Před 5 měsíci

      @@noomade prove it, don't be lame.

    • @noomade
      @noomade Před 5 měsíci

      @@gregorymorse8423 huh? are you r-worded? There are like 20 posts under this video explaining why alloca() cannot be guaranteed to give you the correct answer. You clown 🤡

    • @vytah
      @vytah Před 5 měsíci

      @@gregorymorse8423 alloca isn't standard C. It's not even in POSIX.

  • @user-pw5do6tu7i
    @user-pw5do6tu7i Před 6 měsíci +3

    Upordown is a bad function name IMO. Expected behavior for me would be it nearly always returns true, unless the stack grows horizontally, or non linearly.
    I think these are better:
    bool stackGrowsUp
    bool isGrowingUp

  • @MatteoGodzilla
    @MatteoGodzilla Před 6 měsíci +4

    An idea i had in order to solve this was using inner scopes (i.e `int x; { int y; }`) in order to force the inner variable to be after the outer, but i'm not 100% sure that actually happens

    • @veritas7010
      @veritas7010 Před 6 měsíci +1

      scopes in c don't allocate new stack frames

    • @dobacetr
      @dobacetr Před 6 měsíci +1

      @@veritas7010I think that's the point. You just need to ensure that variables are allocated in order. Hopefully, x is allocated before entering the scope and y is allocated in the scope such that &y-&x is always the direction of the stack.

  • @x1expert1x
    @x1expert1x Před 5 měsíci

    I am in love with this channel as of today. I am done with C# and Java and Python and all the other languages that just have flaming pile of garbage collection behind closed doors.

  •  Před 5 měsíci +1

    As far as I understand the C specification, comparing pointers in this way is undefined behavior, as already explained in other comments. The danger is that such an undefined behavior will probably often work, which makes it hard to discover the problem by testing. One of the most important C skills is to navigate around these undefined behaviors. Therefore, this answer to the interviewer's question arguably shows that, although the interviewee knows things, they more importantly feel too confident writing potentially dangerous code.

  • @CSalvarani3
    @CSalvarani3 Před 6 měsíci +3

    The syntax "if ([cond]) return true; else return false;" terrifies me. Just do "return [cond];".
    Thanks!

  • @Loki-
    @Loki- Před 6 měsíci +4

    It frustrates me to no end that the names are so close together in intuitive meaning that I always forget the difference. A stack of paper is also a heap of paper. Especially annoying that when learning to code stacks, aka first in last out, I'm taught to think of it as stacking napkins up.

    • @gregoryfenn1462
      @gregoryfenn1462 Před 6 měsíci +2

      Not really to be fair, a heap of paper would be like if the sheets are thrown at random everywhere on the table, not ina neat pile. A face-down pile of sheets of paper is a stack, not a heap.

    • @S7hadow
      @S7hadow Před 6 měsíci +2

      For me a heap of paper would be a bunch of paper sheets lying on the floor, all in different orientations and no discernible order. A stack on the other hand is all the papers neatly put together and you can clearly go from top to bottom.
      In that sense, yes, a stack is also a (ordered) heap, but a heap is not necessarily a stack

  • @stevel875
    @stevel875 Před 5 měsíci +1

    On some architectures the result can be non deterministic if called within the context of a larger more complex program ... in at least some implementations of ARM procedure call standards I was familiar with many years ago, stack frames are allocated on the heap as required, so if the inner function call uses the same allocation, the result would be as expected, but if it allocates a new frame on the heap, the result could be either direction.

  • @SC2Tolana
    @SC2Tolana Před 6 měsíci +1

    I'm slightly confused with the stack example shown in the commented code block. In my head a stack is adding to the top, so y would be higher than x.
    Is it because the 'top' of the stack is actually the stack pointer. so when y is getting added "below" x, it's actually getting added to the "top" of the stack ,since that is where the stack pointer would be?

  • @iskamag
    @iskamag Před 5 měsíci +4

    My immediate idea was to use alloca()
    Technically not a standard function, but the 3 only compilers that exist support it.

    • @noomade
      @noomade Před 5 měsíci

      but some people on here have said that alloca() cannot be guaranteed to give the correct answer.

    • @alonamaloh
      @alonamaloh Před 5 měsíci

      Me too! Unlike the C standard, the man page for alloca at least mentions the stack, so we have a better chance of getting the answer we expect. :)

    • @noomade
      @noomade Před 5 měsíci

      @@alonamaloh IFF a stack exists right? and even then you can't guarantee that your alloca() does not get optimized away

    • @alonamaloh
      @alonamaloh Před 5 měsíci

      ​@@noomade Well, a stack must exist if the documentation of alloca says that it "allocates size bytes of space in the stack frame of the caller." If it did anything else, I would say the documentation is wrong.
      You might be right that an aggressive enough compiler might decide to optimize it away, because by most definitions this is not observable behavior.

    • @noomade
      @noomade Před 5 měsíci

      @@alonamaloh I think you should read Grigory Rechistov's posts under this video. He gives compelling arguments as to why alloca is neither a stable nor a guaranteed to be correct solution to this answer (and why no c program can be guaranteed to give the correct answer). He has posts in a few threads with his reasoning.

  • @peterruszel5389
    @peterruszel5389 Před 5 měsíci +14

    please name the function is_up if it's going to return true when up for christ sake

  • @u9vata
    @u9vata Před 6 měsíci +2

    Just an excerpt: "The key thing to understand about the 8 bit PIC architecture is that the stack size is fixed. It varies from a depth of 2 for the really low end devices to 31 for the high end 8 bit devices. The most popular parts (such as the 16F877) have a stack size of 8."
    ^^and they do have C compilers. So there are totally devices with limited hardware stack sizes actually - otherwise its fun little excercise I agree, just sayin' 🙂

  • @ishouldquitwatchingyt
    @ishouldquitwatchingyt Před 6 měsíci +2

    How about using inline assembly? Save rsp at local variable in inline frame #1, allocate some new local variables, then check the changed rsp value at inline frame #2.
    Of course, the local variable should have storage class auto, since static class will make the assembler save variable in other space.

    • @erikkonstas
      @erikkonstas Před 6 měsíci

      With this problem, the architecture itself is in question, and every ISA is different...

  • @ding.1367
    @ding.1367 Před 6 měsíci +4

    hai

  • @jabadoodle
    @jabadoodle Před 5 měsíci +3

    I think you have errors in your code and a number of incorrect/inaccurate statements in your verbalization.

    • @jabadoodle
      @jabadoodle Před 5 měsíci +2

      1: The stack pointer isn't growing "more negative" it's growing "less positive."
      2: Your first method returns the wrong value. Note you return First-Variabe>Second-Variable but in the recursion method it's reversed.
      3: @2:55 you mention X growing. X isn't growing. Neither is it's pointer. You mean if the difference between &X and &Y is growing.
      4: @4:14 you say method one isn't the most efficient. The problem with method-one isn't efficiency, it's that it could be wrong. Big difference.

  • @raidtheferry
    @raidtheferry Před 2 měsíci

    Very cool demonstration

  • @aayushjoshi4937
    @aayushjoshi4937 Před 6 měsíci

    I don't know much about low level programming but i have heard c has function pointers can't we use them in some sense there while using the recursion solution

  • @huffmancollins614
    @huffmancollins614 Před 5 měsíci

    Does it have to be a recursive function or can I have a helper function just return the address of one of it's local variables? Then the first function does the address comparison.

  • @stuartwilson4960
    @stuartwilson4960 Před 5 měsíci +1

    I feel this would be better explained with the assembly listing and a memory view open. The stack could be initialized in different ways, also without the recursion there is pretty much no guarantee that it will grow the stack in any direction. The only correction I would make for intel is to say that the compiled code will use base pointer register offsets (EBP), and not the stack pointer directly, so between call frames there will be some stack preservation wrapper copying to and from EBP/ESP.

  • @asdfghyter
    @asdfghyter Před 6 měsíci

    any particular reason for using recursion instead of two separate functions?

  • @bonny4
    @bonny4 Před 5 měsíci

    I tried this and I was wondering why it shows up for my x86 cpu. It turns out that address sanitizer changes the stack mechanism so that it shows up on my system.

  • @BeeepBo0op
    @BeeepBo0op Před 6 měsíci

    Could this also be solved without recursion by allocating the variables as part of an array, then perform the same check using the two variables?

  • @gudneighbour
    @gudneighbour Před 6 měsíci

    Man thats really great Q for an interview.

  • @user-jn4sw3iw4h
    @user-jn4sw3iw4h Před 5 měsíci

    I agree it is an interesting question, to see what the interviewee knows.
    For example, I haven't used C in a long time and (once managed to set aside the "why do we care")
    I would have guessed the initial solution, and admitted that would be a guess.
    The then following explanation on, what is considered "common-knowledge" on the abilities or lack thereof of compilers.
    Is indeed something I would not have been able to contribute to.
    "Making assumptions on how the compiler treats effects triggered by a single command" (the 2 initializations) was easy to spot as
    "If the compiler breaks the plan. It would likely do so here".
    To which my first thought was:
    - initialize/assign
    - read address
    - initialize/assign
    - read address
    Might solve it, if we want to be more sure, we probably could:
    initialize/assign y, with the value of... the address of x.
    As this information needs to exist, the compiler would have even less room to outsmart the programmer.
    That C-compilers have a "magical, can't optimize beyond this"-border around recursion. Is not something I knew.
    (Again, making it a good question.)

  • @pqnet84
    @pqnet84 Před 6 měsíci

    There is more to that. To account for the inlining and constant propagation you may need to let the number of recursion be dependent on a parameter that the compiler cannot evaluate at compile time, moreover optimization of C allows the compiler to do whatever they want with the pointer comparison, and since the variable are otherwise unused there is no guarantee that an aggressive optimization will not invent whatever result they want for your function. I would probably also use the pointer passed recursively to actually compute something that can't be optimized away (for example, you can keep a running total. I would also if possible rely on some syscall to make sure that the input of the recursive function cannot be guessed at compile time (i.e. using fopen to attempt reading a file) and to use the result of the computation so that it is not discarded and optimized.