One the biggest benefits of separating programs into code and data sections is that it allows the code section to made read-only, while the data sections can be kept writable. This protects the code section from being accidentally modified by the program, and also allows it to be shared between processes running the same program.
More recently the ability to only allow the execution of code in the code section has become important. This is because a number of exploits depend on being able to execute code outside of the code section. Another more recent benefit of separating code and data is that out-of-order CPUs could end up speculatively executing data resulting in poorer performance if code and data were intermingled.
The bss section exists as an extension of the data section. It contains all the program's data that is initialized to zero. By separating the zero initialized data out like this it allows the the bss section to not actually be stored in the program's executable. The both saves disk space and speeds up loading. The bss section in memory is simply filled with zeros rather than reading it into memory.
The heap and stack are similar in that they're both used to allocate objects dynamically as the program runs. The difference between the two is that objects allocated on the heap can be freed at anytime, while anything put on the stack can only be removed after everything put on the stack after it is removed. Every time a function is called space gets allocated on the stack for things like arguments to the function, the return address and local variables. When the function returns these things get removed from the stack. Pretty much anything else that gets dynamically allocated is allocated on the heap.
Traditionally on Unix systems the heap was located in memory after the bss section, which appears after the code and data sections. As things got allocated on the heap it grew upwards to make space for them as necessary. The stack was placed at the end of memory, and grew downwards.