In the previous article, we looked at how to use functions from C code. Today we will look at C code.
Last time, we created a static library from C code. However, V can also work with object files directly. Let’s look at fib.v file again:
module fib #flag -L @VMODROOT/c/ #flag -lfib #flag -I @VMODROOT/c/ #include "fib.h" fn C.fib(num int) int pub fn fib(num int) int { return C.fib(num) }
Instead of linking a library, you can write the path to an object file:
module fib #flag @VMODROOT/c/fib.o // ^^^^^^^^^^^^^^^^^ path to object file #flag -I @VMODROOT/c/ #include "fib.h" fn C.fib(num int) int pub fn fib(num int) int { return C.fib(num) }
After compiling, you will get the same result. When V compiles the code, it will pass the necessary paths to the C compiler, just as it does for libraries.
What’s more, with the #flag
directive, you can pass a C file directly and it
will work:
module fib #flag @VMODROOT/c/fib.c #flag -I @VMODROOT/c/ #include "fib.h" fn C.fib(num int) int pub fn fib(num int) int { return C.fib(num) }
This is useful if you need to include a single file without having to create a library or object file.
Useful flags
When you’re working with C code, it’s useful to know exactly how V compiles your code with the C compiler, as well as what code V produces.
-showcc
Shows all options that will be passed to the C compiler. Let’s try to build with this flag and see what it produces:
v -showcc .
output:
> C compiler cmd: 'cc' '@/tmp/v_501/c_from_v.14958817527713629059.tmp.c.rsp'
> C compiler response file "/tmp/v_501/c_from_v.14958817527713629059.tmp.c.rsp":
-std=c99
-D_DEFAULT_SOURCE
-D GC_BUILTIN_ATOMIC=1
-D MPROTECT_VDB=1
-D GC_THREADS=1
-I ".../v/thirdparty/libgc/include"
-I ".../c_from_v/c"
-x objective-c
-x none
-mmacosx-version-min=10.7
-ldl
-lpthread
-o ".../c_from_v/c_from_v"
".../v/thirdparty/tcc/lib/libgc.a"
".../.vmodules/cache/cf/f1d.module.fib.o"
"/tmp/v_501/c_from_v.14958817527713629059.tmp.c"
I formatted it to make it more pleasant to work with.
V passes many flags, but we’re interested in a few; the .tmp.c file, for example, describes the path to a C-generated file, which we’ll look at later.
f1d.module.fib.o is the path to the object file we passed earlier, V cached it and moved it to its folder.
If you compile the program with this flag when we passed the path to the library, then the lines will be added there:
-L ".../c_from_v/c"
-lfib
Which we described in the fib.v file.
-keepc
We’ve already seen that the output with the -showcc
flag shows us the path to
the C file, but by default it is removed after compilation so that it is not
deleted by passing the -keepc
flag.
It will also make the path to the C file always the same, which is useful when
the file is open in the editor.
Usually, when you’re working with C code, these two flags should be used
together.
-o file.c
You can also compile V code directly into a C file using the -o
flag. The
resulting file will be the same as the one obtained from the path in the output
with the -showcc
flag.
These aren’t all the flags that are available for the C backend. You can see all with the following command:
v help build-c
Generated C code
When you’re working at the intersection of V and C, looking at the resulting C code can be very useful. V was originally designed to produce readable C code that can be read and easily modified for debug.
When you first open the generated file, it may seem scary and very large to you, but this is only at first glance.
Let’s open the generated file for our example above.
To find our code that we wrote in the main
function, just find the function
named main_main
:
VV_LOCAL_SYMBOL void main_main(void) { println(int_str(fib__fib(10))); }
It’s quite simple, isn’t it?
Here we see a call to the builtin println
function, a function to convert a
number to a string, and our function to calculate the fibonacci number.
As you can see, the fib
function from the fib
module has become
the fib__fib
function, V uses the fully qualified name fib.fib
and replaces
dots with two underscores, so if you see a symbol like X__Y
, then X is the
module name and Y is the symbol name. Now it makes sense why the main
function
from main
module became main__main
doesn’t it?
Let’s see what code was generated for the fib
function:
int fib_fib(int num) { int_t1 = fib(num); return_t1; }
V code:
pub fn fib(num int) int { return C.fib(num) }
Pretty similar, only in C there is an additional variable that will be optimized by the C compiler.
If you try, you won’t find the definition of the fib
function in the file,
because its definition is in the file c/fib.h that was included in this C
file.
You can try to find this include
in the file:
#if __has_include("fib.h") #include "fib.h" #else #error VERROR_MESSAGE Header file "fib.h", needed for module `fib` was not found. Please install the corresponding development headers. #endif
V inserts additional checks for file inclusions to give errors early on when the code is only compiled and not linked.
You can try to modify the resulting C code, compiling it into a binary file is
very easy, just take the output with the -showcc
flag and execute it in the
terminal.