Materials are globally stored in:
bpy.data.materials
which is an array of materials that you can index per name or per index.
On the other hand, an object has an array of material slots (which correspond to what you see in the material panel of the object):
obj.material_slots
And get the material from the slot is:
obj.material_slots[i].material
and it also has the list of the materials it is using in its data:
obj.data.materials
Each polygon of a mesh knows the index of the material it is using. This index is the index in obj.material_slots (or obj.data.materials):
p = obj.data.polygons[0]
p.material_index
So to answer your question, it depends of what you want to do.
If the goal is to know which materials are referenced by an object, look at obj.data.materials.
If the goal is to know which are really used, you'll need to dig into polygons to check which is really assign to them (you can have more mats in obj.data.materials than the one really used).
Notice that a material slot can be empty. In this case, the material_slot exists but contains no material. And also, obj.data.materials contains an empty element (as these two arrays are internally kept at the same size).
To have all the materials references by the objects of a scene, in a dictionary by name, you can:
import bpy
all_referenced_materials = set((m for o in bpy.context.scene.objects if o.type == 'MESH' for m in o.data.materials if m is not None))
dictionary = {m.name:m for m in all_referenced_materials}
Note: if I remember well, 16 material slots max. So no more than 16 materials per object.