I am studying the difference between an .EXE program and a .COM program. The .EXE is logical to me that the stack is in another segment with respect to the program code (in fact a stack is forced and to indicate a limit of this with .STACK), therefore, when I go to insert values in the stack (in an .EXE), using a different segment than the program, I'm not going to confuse these two.
Example:
SP = 0400
SS = 3996 CS = 3995 IP = 0000
The stack has a capacity of 1024 Byte (400h) and refers to the stack segment 3996h, which is different from the code segment which is 3995h. So I'm sure the data doesn't get confused.
The thing I don't understand though is when I have to deal with .COM programs; because I know very well that they use only one segment and I find myself with a situation similar to this:
SP = FFEE
SS = 114A CS = 114A IP = 0100
I have the stack segment which matches the code segment. So if I keep putting values on the stack, will they end up in my code sooner or later?
While MS-DOS does set all the segment registers to the same segment on program entry after loading a COM file, there's nothing stopping you from changing the registers to use different segments. However the hard separation between segments you're envisioning doesn't really exist. Even with a separate stack segment, if you overflow your stack you'll still be writing to memory somewhere you shouldn't be and the results will be unpredictable.
Using your second example of CS:IP being 114A:0100 and SS:SP being 114A:FFFE, your COM program could change SS:SP to 210A:03FE. Both SS:SP values would point to the same linear address (2149E) and the same return value that MS-DOS pushed on the stack on entry. You would now only have 1024 bytes for the stack, like in your in EXE example, but your stack segment is still 64k. In real mode, segments are always 64k.
Now guess what happens when SS:SP is 210A:0000 and you try to push a word onto the stack? The CPU doesn't generate an exception, it doesn't somehow refuse to perform the operation, it just does what it always does. It subtracts 2 from SP and then stores the word at the new SS:SP address. In this case that means SP will wrap around to FFFE and the value you push will be stored at 210A:FFFE or at a linear address of 3109E.
Now the question is what is allocated at linear address 3109E? Chances are that MS-DOS allocated this memory to your program and writing values there is harmless since your program isn't using it for anything. However there's a chance that some TSR or driver has allocated the memory there and overwriting it will cause a crash or other unpredictable behaviour.
It would actually be better if you overwrote your own code when the stack overflowed as this would mean you'd be much more likely to notice the bug and fix it.
The same wrap around problem also exists with EXE's. If you want a truly isolated stack you'd need to make 64K in size, but then what happens on overflow is that the stack overwrites itself so doesn't fundamentally eliminate the problem. Ultimately it's your responsibility as a developer to ensure that your program has allocated itself enough stack space and that your code never exceeds that limit.
Note that there's one stack related gotcha you need to worry about with COM files, and that is there's no guarantee that MS-DOS will be able to allocate a full 64K for your COM file. MS-DOS will allocate the biggest free chunk of memory that your COM file will fit into. If that chunk of memory is less than 64K then SP won't be set to FFFE, instead it will set SP to point to the last word allocated to your program. In the worst case that means the stack will immediately start overwriting the code and data in your COM file. For that reason it's a good idea to allocate space at the end of your COM file to reserve space for the stack.