There are already good answers here. Let me add some details (initially, part of this was in a comment).
In simple C, the purpose of each function call is determined during the call (unless you use function pointers). C ++ adds virtual functions for which the actual function called by the call is determined at runtime (dynamic dispatch, late binding). Function pointers allow you to customize sending mechanisms to some extent, but you need to program them yourself.
In Smalltalk, all messages are sent dynamically. In terms of C ++, this roughly means: all member functions are virtual, and there are no autonomous functions (there is always a receiver). Therefore, the Smalltalk compiler never decides which method will be called by sending the message. Instead, the invoked method is determined at run time by a virtual machine that implements Smalltalk.
One of the ways to implement virtual function scheduling is through virtual function tables. The approximate equivalent in Smalltalk is method dictionaries. However, these dictionaries change, unlike typical virtual function tables, which are generated by the C ++ compiler and do not change at run time. All Smalltalk behaviors ( Behavior , which are the superclass of Class ), have such a dictionary of methods. As @ aka.nice noted in his answer, method dictionaries can be requested. But methods can also be added (or removed) while the Smalltalk system is running. When the Smalltalk VM sends a message, it looks in the method dictionaries for the chain of the receiver superclass for the correct method. Caches are commonly used to avoid the recurring cost of this search.
Also note that messaging is the only way objects interact in Smalltalk. Two objects cannot access other instance variables, even if they belong to the same class. In C ++, you can write code that breaks this encapsulation. Therefore, sending messages is fundamental in Smalltalk, whereas in C ++ it is basically an optional feature.
In C ++, Java, and similar languages, there is another form of dispatch called function overloading. This happens exclusively at compile time and selects a function based on the declared argument types on the call site. You cannot affect it at runtime. Smalltalk obviously does not provide this form of submission because it does not have static typing of variables. It can be implemented, however, using idioms such as double dispatch . Other languages, such as Common Lisp CLOS or Groovy, provide even more general multiple dispatch, which means that the method will be selected based on both the receiver type and the runtime types of all arguments.
* Some special messages, such as ifTrue: ifFalse: whileTrue: are usually compiled directly into conditional branches and transitions in bytecode, and not in messages. But in most cases this does not affect semantics.