我愿意使用一种交互式语言来测试旧项目中的一些C代码。我了解一点Forth,但是我从未在现实世界的项目中使用过它。我正在看pForth。
使用交互式Forth解释器测试C程序中某些功能的行为是否合理?此C代码具有许多结构,指向结构的指针,句柄以及C中发现的其他常见结构。
我想我必须编写一些粘合代码来处理参数传递,还可能需要在Forth端进行一些结构分配。我想要一个在该领域有经验的人的估计。这值得么?
最佳答案
如果您要进行交互式测试并针对嵌入式平台,那么Forth无疑是一个不错的选择。您将始终找到在目标平台上运行的Forth实现。如果需要,写一个都不难。
与其编写特定于您当前需求的粘合代码,不如将其用于Forth到C的通用接口。我使用gforth's generic C interface,它非常易于使用。对于Forth中的结构处理,我使用了MPE style implementation,它在与C接口时非常灵活(不过请注意正确的对齐方式,请参阅gforth%align /%allot / nalign)。
通用结构处理字的定义大约需要20行Forth代码,对于单个链表处理或哈希表也是如此。
由于您不能使用gforth(仅适用于POSIX),因此请为您选择的Forth编写扩展模块,以实现类似的C接口。只需确保您的Forth和C接口模块使用与要测试的C代码相同的malloc()和free()。
有了这样的接口,您只需定义存根字词(即将Forth字词映射到C函数和结构)就可以完成Forth中的所有操作。
这是一个示例测试会话,其中我使用gforth的C接口调用libc的gettimeofday
。
s" structs.fs" included also structs \ load structure handling code
clear-libs
s" libc" add-lib \ load libc.so. Not really needed for this particular library
c-library libc \ stubs for C functions
\c #include <sys/time.h>
c-function gettimeofday gettimeofday a a -- n ( struct timeval *, struct timezone * -- int )
end-c-library
struct timeval \ stub for struct timeval
8 field: ->tv_sec \ sizeof(time_t) == 8 bytes on my 64bits system
8 field: ->tv_usec
end-struct
timeval buffer: tv
\ now call it (the 0 is for passing NULL for struct timezone *)
tv 0 gettimeofday . \ Return value on the stack. output : 0
tv ->tv_sec @ . \ output : 1369841953
请注意,
tv ->tv_sec
实际上与C中的(void *)&tv + offsetof(struct timeval, tv_sec)
等效,因此它为您提供了结构成员的地址,因此您必须使用@
来获取值。这里还有另一个问题:由于我使用的是64位的Forth,其中单元大小为8个字节,因此存储/获取8个字节长的内容很简单,但是获取/存储4个字节的int则需要进行一些特殊处理。无论如何,Forth都使这变得容易:只需为此定义特殊用途的int@
和int!
字即可。如您所见,有了良好的通用C接口,您无需用C编写任何胶合代码,只需要用于C函数和结构的Forth存根,但这确实很简单(而且大多数可以自动从您的C标头生成)。
对交互式测试感到满意之后,您可以继续进行自动化测试:
将整个输入/输出从交互式测试会话中复制/粘贴到名为testXYZ.log的文件中
从会话日志中剥离输出(仅保留输入),并将其写入名为testXYZ.fs的文件
要运行测试,请将testXYZ.fs用管道传输到您的第四个解释器,捕获输出,然后使用testXYZ.log进行比较。
由于从交互式会话日志中删除输出可能有些乏味,因此您也可以首先编写测试脚本testXYZ.fs,然后运行它并捕获输出testXYZ.log,但是我更喜欢从交互式会话日志开始。
等等!
作为参考,这是我在以上示例中使用的结构处理代码:
\ *****************************************************************************
\ structures handling
\ *****************************************************************************
\ Simple structure definition words. Structure instances are zero initialized.
\
\ usage :
\ struct foo
\ int: ->refCount
\ int: ->value
\ end-struct
\ struct bar
\ int: ->id
\ foo struct: ->foo
\ 16 chars: ->name
\ end-struct
\
\ bar buffer: myBar
\ foo buffer: myFoo
\ 42 myBar ->id !
\ myFoo myBar ->foo !
\ myBar ->name count type
\ 1 myBar ->foo @ ->refCount +! \ accessing members of members could use a helper word
: struct ( "name" -- addr 0 ; named structure header )
create here 0 , 0
does>
@ ;
\ <field-size> FIELD <field-name>
\ Given a field size on the stack, compiles a word <field-name> that adds the
\ field size to the number on the stack.
: field: ( u1 u2 "name" -- u1+u2 ; u -- u+u2 )
over >r \ save current struct size
: r> ?dup if
postpone literal postpone +
then
postpone ;
+ \ add field size to struct size
; immediate
: end-struct ( addr u -- ; end of structure definition )
swap ! ;
: naligned ( addr1 u -- addr2 ; aligns addr1 to alignment u )
1- tuck + swap invert and ;
\ Typed field helpers
: int: cell naligned cell postpone field: ; immediate
: struct: >r cell naligned r> postpone field: ; immediate
: chars: >r cell naligned r> postpone field: ; immediate
\ with C style alignment
4 constant C_INT_ALIGN
8 constant C_PTR_ALIGN
4 constant C_INT_SIZE
: cint: C_INT_ALIGN naligned C_INT_SIZE postpone field: ; immediate
: cstruct: >r C_PTR_ALIGN naligned r> postpone field: ; immediate
: cchars: >r C_INT_ALIGN naligned r> postpone field: ; immediate
: buffer: ( u -- ; creates a zero-ed buffer of size u )
create here over erase allot ;