What you suggest is sort of possible. C doesn't really let you manipulate the stack, and when you call a function, it doesn't know about the local variables around it, so you'd need to allocate a block of memory on the heap to keep some fake "stack space" that you use for your scripting language's local variables, and pass that into each function (or stuff it into a thread-global). You'd also need a base pointer for that stack for your scripting language's function calls.
Once you're doing that, you've already done most of what you'd do to get a stack-based language. So you might as well do the rest. To use the actual stack and base pointer for this, you would have to drop down to a machine language level.
If your language is register-based, it still needs a stack for accessing local variables (it just uses it less often), you just don't use it for instruction parameters as much. If I may criminally simplify, 3-address-register-based VMs are kind of a special case of a stack-based VM.
Another approach for bytecode interpreters is to have the instruction contain the instruction ID, which is then used as an index into an array of function pointers, each of which implements one instruction.
Obviously doing this has performance implications. If what your instructions do is simple enough, you can save CPU cycles by implementing them directly in machine code and foregoing the (usually negligible) overhead of a function call, and maybe even use the real stack instead of the fake one.
It all depends on your needs. For most cases, and especially if this is your first parser/interpreter/VM, I'd recommend going with an array of function pointers and a fake stack. It's simple, not too hard to debug, and fast enough on modern machines. You can always later go in and write an optimized version that does things differently.
E.g. one approach is to generate just enough machine code for a function call, and then insert a pointer to such a function in the generated machine code. So each script becomes a block of compiled code, but you don't have to write a full compiler. From there on, you can then improve individual, pivotal instructions by generating the assembler for them, by leaving less often used things as functions. This improves locality of code a bit, which is one tiny micro-optimization that can help. But just one of many.
Oh, about a month ago, I blogged on how one would make a compiler (and bytecode interpreter) from a beginner's point of view, which may be helpful: http://orangejuiceliberationfront.com/how-to-write-a-compiler/