-2

im trying to understand how this implementation of the stack works, and i have come across this code

while(x-- >= 0){
        token_size = (strlen(argv[x])+1)*sizeof(char);
        *esp = *esp - token_size;
        arr[x] = (uint32_t *)*esp;
        memcpy(*esp, argv[x], strlen(argv[x])+1);
    }

first of all, why do we decrement the stack pointer when we push arguments to the stack, i was under the impression that we add to the end of the stack and take from the front of the stack, so surely we would increment the stack pointer to add to the end of the stack? Please correct me if i am wrong ( which is almost definitely the case ).

back on the topic of the title, in the first part of the while loop the size of string argv[x] has +1 added to it, i am unsure why this is the case, and is this multiplying the string size by sizeof(char) or is this a pointer?

any explanation ( little or large ) is appreciated, thankyou in advance!

m e m e
  • 105
  • 2
  • 11
  • Have you tried running it [here](http://pythontutor.com/c.html#mode=edit) to see what the code is doing? – Random Davis Jan 19 '21 at 16:53
  • When `x` is `0`, you go into the loop with an updated `x` of `-1`... possible UB in `argv[-1]`, `arr[-1]`, ... – pmg Jan 19 '21 at 20:55

1 Answers1

1

The stack may grow either up or down in practice. There's some deep history about computer architecture which led to the stack growing downward on a number of common computer systems, including today's x86 processors that you likely use every day. As long as the stack is used consistently, growing downward is not an issue of functionality (although it brings up some complex security concerns)

With regard to the expression (strlen(argv[x])+1)*sizeof(char);:

We add +1 to make room for a null terminator. In C, strings are simply contiguous not-null characters followed by a null character marking the end. strlen does not count the null for the length (e.g. strlen("Hello") is 5, while the memory needed to store it is six characters when the null is counted).

The * is a multiplication -- it is a binary operator with an operand on both the left and the right side. Asterisks are used to perform pointer indirection in a unary context (e.g. *a = 5 or foo(*b, *c).

nanofarad
  • 40,330
  • 4
  • 86
  • 117
  • hi, thanks for answering my question, so if * in that case was mutliplication, size(char) is always 1, so why is this implementation mutliplying the string length by 1, as the result will be the same as if the mutliplcation did not exist – m e m e Jan 19 '21 at 17:17
  • i should have been more clear with my first question btw, in this case its a stack implementation in PintOS, the included code is an extract from the setup_stack function which is where we push arguments to the stack, the stack is under the LIFO principle, ( last in first out ) so for example if the stack was an array stack[], stack[0] would be the first out, if the stack pointer is pointing to the last in part of the stack, why when we push to the stack do we put the stack pointer closer to the bottom of the stack?, or is decrementing the stack pointer making it further away from stack[0]? – m e m e Jan 19 '21 at 17:21
  • @meme Decrementing the stack pointer when pushing is fine as long as you increment it while popping. The multiplication is really for consistency since you do multiply by one -- it signals a clearer intent that you're allocating storage for chars. – nanofarad Jan 19 '21 at 17:37
  • @meme `*sizeof(char)` is [wet](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself#DRY_vs_WET_solutions). Allocation is already known to relate to `char` due to `strlen()` usage. – chux - Reinstate Monica Jan 19 '21 at 18:43
  • Re “As long as the stack is used consistently, growing downward is not an issue”: Actually, it makes an important difference. Growing downward means that buffer overruns corrupt data earlier on the stack. That includes return addresses, allowing attackers to influence program control flow. If the stack were reversed, buffer overruns would have less opportunity to do damage. – Eric Postpischil Jan 19 '21 at 19:56
  • @EricPostpischil At risk of providing the OP too much irrelevant information, a small note was added. It's worth noting, however, that upwards-growing stacks have "less" opportunity, but still have sufficient risk that bounds checking, stack canaries, ASLR, etc will do orders of magnitude more good than just relying on stack direction. It's simple enough to take control of return addresses on an upwards-growing stack if you ever get to overflow a buffer allocated in an earlier frame. Without extra safeguards, both directions are sufficiently unsafe that comparing them isn't *that* meaningful. – nanofarad Jan 19 '21 at 20:54