Your code compiles because it's syntactically correct and you're using compiler extensions; however, there are some fundamental issues with your code that might be leading to your segfault.
First, your signal handler code:
typedef void (*sighandler_t)(int);
sighandler_t f(int pid) {
    void sigintHandler(int sig) {
        printf("Process %d", pid);
    }
    return sigintHandler;
}
This is not standard C and even requires the -ftrampolines flag be specified on some versions of gcc to actually compile.
Your signal handler function itself has a few issues that need to be resolved:
sigintHandler is a nested function, thus when your signal handler function f returns by return sigintHandler;, you're returning a function pointer.
In your code, this compiles correctly because you have typedef void (*sighandler_t)(int);, which defines a function pointer type that can point to functions that have a void return type and take an int as a parameter, which your sigintHandler is defined as.
Instead, your signal handler function could be written simply as:
void sigintHandler(int sig) {
    printf("Signal %d\n", sig);
}
In your main function, you have the following:
if (signal(SIGTSTP, *f(1)) == SIG_ERR) {
    // ....    
}
Here it should be noted this as well has some issues. First, the signal function takes as its first parameter the signal number (usually a macro defined in the signal.h header) and as it's second argument a pointer to a function defined as void func_name(int sig).
To this, you are calling the function instead of passing it as a pointer.
*f(1) actually makes a call to f passing 1 as its parameter; instead, you would change it to the following:
if (signal(SIGTSTP, f) == SIG_ERR) {
    // ....    
}
But this should emit a warning/error since f is defined as returning a function pointer instead of void.
So to change the code to be compliant, you could just do the following:
#include <stdio.h>
#include <signal.h>
void sigintHandler(int sig) {
    printf("Signal %d", sig);
}
int main(void) {
    // ...
    if (signal(SIGTSTP, sigintHandler) == SIG_ERR) {
        // ...
    }
    // ...
    return 0;
}
You stated however:
To have variable behaviour ...
This depends on what kind of variable nature you're intending, but if it's variable functions based on the signal, you can do something like the following:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_stop(int sig) {
    printf("Process %d stop\n", getpid());
}
void sig_int(int sig) {
    printf("Process %d interrupt\n", getpid());
}
int main(void) {
    // ...
    if (signal(SIGTSTP, sig_stop) == SIG_ERR) {
        // ...
    }
    if (signal(SIGINT, sig_int) == SIG_ERR) {
        // ...
    }
    // ...
    return 0;
}
Or you could use a switch statement:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigHandler(int sig) {
    printf("Process %d received %d\n", getpid(), sig);
    switch (sig) {
        case SIGTSTP:
            // do stop code
            break;
        case SIGINT:
            // do interupt code
            break;
    }
}
int main(void) {
    // ...
    if (signal(SIGTSTP, sigHandler) == SIG_ERR) {
        // ...
    }
    if (signal(SIGINT, sigHandler) == SIG_ERR) {
        // ...
    }
    // ...
    return 0;
}
any tips on how to debug seg faults in general would be really appreciated!
First, understand what a segmentation fault is; then you can use a debugger like gdb to step through your code or inspect crash dumps to see where specifically the segfault is happening.
Hope that can help.