This probably all gets a bit processor-system specific. It used to be very simple in the pre-protected mode days.
"Segments" make most sense when taken in relation to the segment registers of an x86 processor (incl pentium), where the main ones are
cs = code segment
ds = data segment
ss = stack segment
There are also others (es, fs, and gs) used for pointers etc.
The cs, ds, and ss registers have special characteristics.
For instance, the instruction being handled by the processor is always in an area of memory identified by cs. Its full address will be cs:ip where ip is the instruction pointer. That is more-or-less the only function of cs. Hence it really only changes if you call a far function, where the code branches off to another code segment. But theoretically you could put a whole programme in one code segment and it would never change.
You CAN use cs for other things. For instance, you can mingle data and code, and use cs to address the code. However, at the machine level, this is not a default, and will often involve extra bytes of code to tell the processor how you're addressing, and that slows things down. Besides, it's unnecessary.
ds is the data segment, which means it is used by default if you ask the processor to add, move, subtract or whatever a variable in memory. It's usually the fastest thing to get at. The data segment is inflexible, in that normally ds gets set once and mostly left there. Hence it's very convenient for global variables that have to be available everywhere.
In the old .com tiny model, ds and cs were the same, so in effect data were in the code segment, but they were also in the data segment (it being exactly the same piece of memory), and would be referred to using ds, not cs.
ss is the stack segment, the "target" of pushed and popped data. It's also addressed not only as ss:sp (sp is the stack pointer, the address where pushing and popping is going on), but also as ss:bp in various combinations. bp is a spare pointer (basis pointer) very handy for looking at variables handled on the stack. Hence the stack is ideal for local variables. Parameters passed with the function are nearly identical to local variables, and also referred to relative to ss:bp. The original idea was you could push all the parameters and then call a function, or something similar. Then you could save the current basis pointer and set it to the current stack pointer and you'd be ready to look at all your parameters and local variables. I'm biassed because my understanding is mostly from pascal, but the general principles are similar for all "normal" languages. Books on assembler usually have a chapter about 3 from the end called "interfacing to higher level languages" which explains all this.
The stack is dificult for more globally available variables because it works first in last out, and the return addresses of functions are mingled in amongst space alocated on the stack for other purposes. When you return from a subfunction to the main program, you use this return address, and everything stacked on top of it is liable to be overwritten by the next function you call.
The heap is just all the memory left over after you've accounted for stack, program, and data. In fact, because the stack grows and shrinks, it will normally be set up so that it shares space with the heap, and the compiler just has to avoid the stack and heap growing so much they collide in the middle.
So in terms of programming, a stack-space variable is hard to maintain outside the function to which it belongs. They are almost doomed to be local.
A ds data segment variable is highly likely to occupy its space from start to finish of runtime, so it's probably global.
A heap variable must be pointed at as necessary using one of the remaining segment registers, and the heap is in your control as a programmer: you can make a variable there and leave it until the program closes, or you can dispose of it whenever you want.
Hope this helps, and someone will tell me if I'm writing rubbish...