an indirect call , use of ecx as the this pointer etc indicates it is a virtual function call
lets take the example you quoted in your query modify it a little and see the disassembly
contents of directory pre compilation
D:\virt>dir /b
virt.cpp
source from example duly modified a bit
D:\virt>type virt.cpp
01 #include <iostream>
02 class Animal {
03 public:
04 Animal( char *name,char *color) {_name =name; _color=color;};
05 virtual char *getname(){return _name;}
06 virtual char *makeSound() = 0;
07 virtual char *getColor() {return _color;}
08 virtual ~Animal() {};
09 private:
10 char *_name;
11 char *_color;
12 };
13 class Cat : public Animal{
14 public:
15 Cat(char *name,char* color) : Animal(name,color) {};
16 char *makeSound() { return "meow"; }
17 ~Cat() {};
18 };
19 Animal* animals[] = {
20 new Cat("fluffy"," red "), new Cat("gruffy"," green "),
22 new Cat("mooody","magenta")
23 };
24 int main(){
25 for (int i = 0; i < 3; i++)
26 if (animals[i]){
27 std::cout << animals[i]->getname() << " is ";
28 std::cout << animals[i]->getColor() << " and sounds ";
29 std::cout << animals[i]->makeSound() << std::endl;
30 }
31 for (int i = 0; i < 3; i++)
32 delete animals[i];
33 return 0;
34 }
compiled and executed using vs2017 community in x86 as x86
D:\virt>cl /Zi /W4 /analyze /EHsc /nologo /Od virt.cpp /link /release
virt.cpp
D:\virt>virt.exe
fluffy is red and sounds meow
gruffy is green and sounds meow
mooody is magenta and sounds meow
disassembly of line 26,27,28 which calls the functions notice the pointer arithmetic
and indirect calls highlighted with <<<<<<<<<<<<<<<
D:\virt>cdb -lines -c "g virt!main;uf .;q" virt.exe | grep -iE " 26| 27| 28"
virt!main+0x33 [d:\virt\virt.cpp @ 26]:
26 00251223 6800022c00 push offset virt!__xt_z+0x34 (002c0200)
26 00251228 8b55fc mov edx,dword ptr [ebp-4]
26 0025122b 8b049528892d00 mov eax,dword ptr virt!animals (002d8928)[edx*4]
26 00251232 8b4dfc mov ecx,dword ptr [ebp-4]
26 00251235 8b10 mov edx,dword ptr [eax]
26 00251237 8b0c8d28892d00 mov ecx,dword ptr virt!animals (002d8928)[ecx*4]
26 0025123e 8b02 mov eax,dword ptr [edx] <<<<<<<<<
26 00251240 ffd0 call eax <<<<<<<<<<<<<<<
26 00251242 50 push eax
26 00251243 68688a2d00 push offset virt!std::cout (002d8a68)
26 00251248 e803020000 call virt!std::operator<<<std::char_traits<char>
26 0025124d 83c408 add esp,8
26 00251250 50 push eax
26 00251251 e8fa010000 call virt!std::operator<<<std::char_traits<char>
26 00251256 83c408 add esp,8
27 00251259 6808022c00 push offset virt!__xt_z+0x3c (002c0208)
27 0025125e 8b4dfc mov ecx,dword ptr [ebp-4]
27 00251261 8b148d28892d00 mov edx,dword ptr virt!animals (002d8928)[ecx*4]
27 00251268 8b45fc mov eax,dword ptr [ebp-4]
27 0025126b 8b12 mov edx,dword ptr [edx]
27 0025126d 8b0c8528892d00 mov ecx,dword ptr virt!animals (002d8928)[eax*4]
27 00251274 8b4208 mov eax,dword ptr [edx+8] <<<<<<<
27 00251277 ffd0 call eax <<<<<<<<<
27 00251279 50 push eax
27 0025127a 68688a2d00 push offset virt!std::cout (002d8a68)
27 0025127f e8cc010000 call virt!std::operator<<<std::char_traits<char>
27 00251284 83c408 add esp,8
27 00251287 50 push eax
27 00251288 e8c3010000 call virt!std::operator<<<std::char_traits<char>
27 0025128d 83c408 add esp,8
28 00251290 68f01b2500 push offset virt!std::endl<char,std::char_traits<char>
28 00251295 8b4dfc mov ecx,dword ptr [ebp-4]
28 00251298 8b148d28892d00 mov edx,dword ptr virt!animals (002d8928)[ecx*4]
28 0025129f 8b45fc mov eax,dword ptr [ebp-4]
28 002512a2 8b12 mov edx,dword ptr [edx]
28 002512a4 8b0c8528892d00 mov ecx,dword ptr virt!animals (002d8928)[eax*4]
28 002512ab 8b4204 mov eax,dword ptr [edx+4] <<<<<<<<<
28 002512ae ffd0 call eax <<<<<<<<<
28 002512b0 50 push eax
28 002512b1 68688a2d00 push offset virt!std::cout (002d8a68)
28 002512b6 e895010000 call virt!std::operator<<<std::char_traits<char>
28 002512bb 83c408 add esp,8
28 002512be 8bc8 mov ecx,eax
28 002512c0 e8db1a0000 call ::operator<< (00252da0)
edit :
an array of function pointers will generate indirect calls
like call qword ptr [reg + offset] ,call qword ptr [mem+offset] etc
my answer is more of practical nature if you are looking for a theoretical way to distinguish the nature indirect calls i cant be of much help there shown below is source and disassembly of an array of function pointers
source compiled and executed
:\>type funarr.cpp
#include <stdio.h>
#include <stdlib.h>
int sum(int a, int b){ return a + b;}
int sub(int a, int b){ return a - b;}
int mul(int a, int b){ return a * b;}
int (*p[3]) (int x, int y) {sum,sub,mul};
int main(int argc,char *argv[])
{
if(argc == 3)
{
int i = atoi(argv[1]);
int j = atoi(argv[2]);
int k = (*p[0]) (i, j);
int l = (*p[1]) (i, j);
int m = (*p[2]) (i, j);
printf("%d %d %d", k,l,m );
}
}
:\>cl /Zi /W4 /analyze /O1 /nologo funarr.cpp /link /release
funarr.cpp
:\>funarr.exe 2 3
5 -1 6
disassembly of main
:\>cdb -c "uf funarr!main;q" funarr.exe | awk "/Reading/,/quit/"
0:000> cdb: Reading initial command 'uf funarr!main;q' funarr!main: 00007ff7`95f2106c 48895c2408 mov qword ptr [rsp+8],rbx
00007ff7`95f21071 48896c2410 mov qword ptr [rsp+10h],rbp
00007ff7`95f21076 4889742418 mov qword ptr [rsp+18h],rsi
00007ff7`95f2107b 57 push rdi
00007ff7`95f2107c 4883ec20 sub rsp,20h
00007ff7`95f21080 488bda mov rbx,rdx
00007ff7`95f21083 83f903 cmp ecx,3
00007ff7`95f21086 754c jne funarr!main+0x68 (00007ff7`95f210d4)
funarr!main+0x1c:
00007ff7`95f21088 488b4a08 mov rcx,qword ptr [rdx+8]
00007ff7`95f2108c e803810200 call funarr!atoi (00007ff7`95f49194)
00007ff7`95f21091 488b4b10 mov rcx,qword ptr [rbx+10h]
00007ff7`95f21095 8be8 mov ebp,eax
00007ff7`95f21097 e8f8800200 call funarr!atoi (00007ff7`95f49194)
00007ff7`95f2109c 8bd0 mov edx,eax
00007ff7`95f2109e 8bcd mov ecx,ebp
00007ff7`95f210a0 8bf8 mov edi,eax
00007ff7`95f210a2 ff1558cf0500 call qword ptr [funarr!p (00007ff7`95f7e000)]
00007ff7`95f210a8 8bd7 mov edx,edi
00007ff7`95f210aa 8bcd mov ecx,ebp
00007ff7`95f210ac 8bf0 mov esi,eax
00007ff7`95f210ae ff1554cf0500 call qword ptr [funarr!p+0x8 (00007ff7`95f7e008)]
00007ff7`95f210b4 8bd7 mov edx,edi
00007ff7`95f210b6 8bcd mov ecx,ebp
00007ff7`95f210b8 8bd8 mov ebx,eax
00007ff7`95f210ba ff1550cf0500 call qword ptr [funarr!p+0x10 (00007ff7`95f7e010)]
00007ff7`95f210c0 448bc3 mov r8d,ebx
00007ff7`95f210c3 488d0d76c20400 lea rcx,[funarr!`string' (00007ff7`95f6d340)]
00007ff7`95f210ca 448bc8 mov r9d,eax
00007ff7`95f210cd 8bd6 mov edx,esi
00007ff7`95f210cf e818000000 call funarr!printf (00007ff7`95f210ec)
funarr!main+0x68:
00007ff7`95f210d4 488b5c2430 mov rbx,qword ptr [rsp+30h]
00007ff7`95f210d9 33c0 xor eax,eax
00007ff7`95f210db 488b6c2438 mov rbp,qword ptr [rsp+38h]
00007ff7`95f210e0 488b742440 mov rsi,qword ptr [rsp+40h]
00007ff7`95f210e5 4883c420 add rsp,20h
00007ff7`95f210e9 5f pop rdi
00007ff7`95f210ea c3 ret
quit: