by Theo Verelst
FOSDEM update!
When presenting BWise at fosdem, I had prepared this example as a demonstration, and of course Murphy had to come along and spoil it all..
In short there seemed to be no way to get this bwise/C program/image view block chain to work without error also when I tried it again later in Belgium. A few days later I tried again, and found that the
exec sine.exe
in essence fails to operate under tcl (wish) 8.4.4 from activestate, It generates a, for me, as yet untraceable 'out of bounds' error of some kind.
This page now contains a quick fix for the problem, which could be caused by cygwin or by some stack/heap space inside a process problem, consisting of adding a -mno-cygwin flag to the compile command. Then all runs smooth and well (even pretty fast) again.
I also changed a loop condition in the C program to let max represent the actual maximum of the sine number (small change).
2004-03-02: I've upgraded cygwin to the latest version, and the problem hasn't repeated itself, without changing anything else. Well well.
GPS: Hi Theo. I have found such memory problems and it has often been an off by 1 error in my C code. The Electric Fence tool helps for debugging heap memory usage errors. There is also another tool that works with Linux called Valgrind. Some of the Tcl developers use it and IIRC there is a target in the Makefile for running Tcl with Valgrind.
TV: Well, I make lots of spelling mistakes when I'm sort of free-wheeling, definitely have 'check-for-missing-semi-colon' high on my debugging priority list, and am very aware of implicit C memory models, but I think I rarely have off-by-one errors as you describe. The off by one was in the loop for number of graphs being drawn, where I didn't define whether max was defined as base 0 (C arrays) or 1 (human interface), and I decided I'd make it the latter.
You can prevent off-by-one by fairly simple rules (which of course you can easily decide to break):
int array[100] for (i=0; i<100; i++) array[i] = i;
Its not bad to know about such tools you mention, though I'd think that for normal C programs indeed that would be for testing for the equivalent of typos, it shouldn't be that necessary.
OO systems have memory problems, either when in buildup (object creation, referencing, deletion), or when some kind of garbage collection is used. Often that is not desirable at all, just program right. In some cases, like recently someone mentioned in tclHttpd, you'd need to decide how long certain data must be continued to live in core.
Memory leaks often is either wrong programming in the most normal sense of the expression, which you definitely may want to search for, but traditionally, it has also another meaning.
In C, where tcl/tk is programmed in of course, the main construct coming from unix (or maybe before that, it came from somewhere else, still) to (dymanically) claim memory for a program is the malloc((size_t) size) functions, which returns pointer to a contiguous piece of main memory, in addressing according to the current process (virtual) address space.
A possible leak is when you forget to free(pointer) that memory again when you're done with it. But more importantly for 'normal' programmers, a leak appears when you allocating and deallocating a lot of memory segments in sequence, which is not a programming error, but a malloc implementation issue. For instance when you want to malloc space for one byte (bit ridiculous example) probably malloc assigns at least 4 bytes, to align to machine words.
So when you allocate space using unmodified system malloc, 1 million mallocs for a byte actually takes 4MBytes (actually 4 million bytes).
A bad 'leak' in practical sense can come from the management of memory when segments are allocated/deallocated/reallocated, of different sizes. With no paging, when the OS assigns an amount of memory to a process, you start filling that up with your program and its data, and somewhere you start assigning memory for malloc. When you at some point have a pretty large amount of malloc-ed data, and you dealloc some, you get 'holes' in the pool, which can be filled by new mallocs of course, assuming that fits. When the new mallocs don't fit, the holes remain, making your process actual memory space grow while you don't use much more memory.
Paging may make this better, where a low layer of the computer simply maps those virtual memory pages who are not anymore claimed by mallocs who were freed away from the process, to make the physical memory in the holes available for others.
But paging, as the word says, works with pages, for instance a few kilobytes large, so you will still in a sense have memory on the boundaries of pages 'leaked' all the time.
I don't know how Windows and Linux deal with paging.
A possible other memory error is that normally, the OS will assign subsequent malloc requests to subsequent memory areas, even contiguous, depending on OS. So in principle, when you would
p1 = malloc(1000); malloc(1000);
You could (but should not!)
p1[1500] = '\a';
or something, and end up in the middle of the second memory piece. Obviously, when the mallocs happen not to end up contiguous, or got some struct/union in their beginning with OS data, you're in trouble.
Making a malloc which can deal with small mallocs, neat use of realloc(), and with a decent memory partitioning algorithm for your application is a decent job to begin with for a heavy and reliable application.
To answer the suggestion: I don't think there were too-high-index errors (though I could have overlooked) I didn't change anything, the loops seem ok at glance, it seems the cygwin library might have been the wrong version (a cygwin1.dll in the 'current directory' surviving a cygwin upgrade for instance).
The progam worked fine when I made it on linux, and tried it (over common disc partition) on cygwin/windows. It just had broken down for the presentation, maybe because I upgraded cygwin or kde for windows or something.
When you see an error, or find one when you try it yourself, by all means let me know.
I'm starting this page as a sketch of the idea that a c compiler can be run as part of a bwise canvas, where compilation is automatic, and followed by automatic execution of the resulting program, and in the first example showing of the resulting image on-canvas.
Sketch doesn't mean the method doesn't in principle work without (much) adaptation in general, but I'm not done doing a streamlined XP/linux automatic version, a few things require hand actions.
The following two files are needed in the current directory where bwise (one file version: [L1 ] must be running, under I guess any reasonable version of tcl/tk. You need cygwin on windows to have a good C compiler with unix interface, which I used in the example, or you can be running on Linux, where the current user must have access right to gcc. On windows, you may have to set (depending on your configuration):
append env(PATH) ";c:\\cygwin\\bin"
I made this and some more examples first on linux (Redhad 9, compiled tk 8.4), while I made this page using a windows (XP with precompiled tcl8.4) running version from the same files and file system, except I had some versions, I think they should interchange without work. The 'newimage sine.ppm' command, which can generate a block with image in one stroke, isn't in the saved canvas, because then blocks are saved as individual parts, linked implicitly by tags under bwise, so I had to edit the saved canvas content file to prepare a block of size 512 x 512 because at first run, there isn't an sine.ppm image yet.
The first block should be 'funprop'-ed through the popup menu to get the chain going and produce the output image. Depending om machine speed that takes a number of seconds, also the second block, which runs after the compiler compiled the small program, which produces the image, takes a little while to run, depending on machine speed, I used fairly fast machines. Of course it also depends on the OS..
# file: cansine6.tcl global bcount scopeindex wireindex shellindex drumindex entrycount moncount proccount seqcount stackcount termindex textcount set bcount 0 set scopeindex 0 set wireindex 5 set shellindex 0 set drumindex 0 set entrycount 3 set moncount 1 set proccount 2 #image create photo sine -file sine.ppm image create photo sine -width 512 -height 512 .mw.c create rectangle 246.0 10.0 286.0 40.0 -activedash {} -activefill {} -activeoutline {} -activeoutlinestipple {} -activestipple {} -activewidth 0.0 -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledoutline {} -disabledoutlinestipple {} -disabledstipple {} -disabledwidth 0 -fill yellow -offset 0,0 -outline darkblue -outlineoffset 0,0 -outlinestipple {} -state {} -stipple {} -tags {Proc1 newblock block} -width 1.0 .mw.c create text 266.0 40.0 -activefill {} -activestipple {} -anchor n -disabledfill {} -disabledstipple {} -fill darkblue -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc1 crb label} -text Proc1 -width 0 .mw.c create text 245.0 29.0 -activefill {} -activestipple {} -anchor se -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc1 crb pinname in} -text in -width 0 .mw.c create line 226.0 30.0 246.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc1 newblock pin in typein} -width 2.0 .mw.c create text 287.0 29.0 -activefill {} -activestipple {} -anchor sw -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc1 crb pinname out} -text out -width 0 .mw.c create line 306.0 30.0 286.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc1 newblock pin out typeout} -width 2.0 .mw.c create rectangle 351.0 10.0 391.0 40.0 -activedash {} -activefill {} -activeoutline {} -activeoutlinestipple {} -activestipple {} -activewidth 0.0 -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledoutline {} -disabledoutlinestipple {} -disabledstipple {} -disabledwidth 0 -fill yellow -offset 0,0 -outline darkblue -outlineoffset 0,0 -outlinestipple {} -state {} -stipple {} -tags {Proc2 newblock block} -width 1.0 .mw.c create text 371.0 40.0 -activefill {} -activestipple {} -anchor n -disabledfill {} -disabledstipple {} -fill darkblue -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc2 crb label} -text Proc2 -width 0 .mw.c create text 350.0 29.0 -activefill {} -activestipple {} -anchor se -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc2 crb pinname in} -text in -width 0 .mw.c create line 331.0 30.0 351.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc2 newblock pin in typein} -width 2.0 .mw.c create text 392.0 29.0 -activefill {} -activestipple {} -anchor sw -disabledfill {} -disabledstipple {} -fill black -font {Helvetica -12} -justify left -offset 0,0 -state {} -stipple {} -tags {Proc2 crb pinname out} -text out -width 0 .mw.c create line 411.0 30.0 391.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {Proc2 newblock pin out typeout} -width 2.0 .mw.c create line 306.0 30.0 331.0 30.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {wire1 connect wire Proc1 out Proc2 in} -width 1.0 .mw.c create rectangle 53.0 72.0 565.0 584.0 -activedash {} -activefill {} -activeoutline {} -activeoutlinestipple {} -activestipple {} -activewidth 0.0 -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledoutline {} -disabledoutlinestipple {} -disabledstipple {} -disabledwidth 0 -fill yellow -offset 0,0 -outline darkblue -outlineoffset 0,0 -outlinestipple {} -state {} -stipple {} -tags {sine newblock block} -width 1.0 .mw.c create text 309.0 584.0 -activefill {} -activestipple {} -anchor n -disabledfill {} -disabledstipple {} -fill darkblue -font {{MS Sans Serif} 8} -justify left -offset 0,0 -state {} -stipple {} -tags {sine crb label} -text sine -width 0 .mw.c create text 52.0 91.0 -activefill {} -activestipple {} -anchor se -disabledfill {} -disabledstipple {} -fill black -font {{MS Sans Serif} 8} -justify left -offset 0,0 -state {} -stipple {} -tags {sine crb pinname in} -text in -width 0 .mw.c create line 33.0 92.0 53.0 92.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {sine newblock pin in typein} -width 2.0 .mw.c create text 566.0 91.0 -activefill {} -activestipple {} -anchor sw -disabledfill {} -disabledstipple {} -fill black -font {{MS Sans Serif} 8} -justify left -offset 0,0 -state {} -stipple {} -tags {sine crb pinname out} -text out -width 0 .mw.c create line 585.0 92.0 565.0 92.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {sine newblock pin out typeout} -width 2.0 .mw.c create image 53.0 72.0 -activeimage {} -anchor nw -disabledimage {} -image sine -state {} -tags {sine newimage block image} .mw.c create line 411.0 30.0 33.0 92.0 -activedash {} -activefill {} -activestipple {} -activewidth 0.0 -arrow none -arrowshape {8 10 3} -capstyle butt -fill darkblue -dash {} -dashoffset 0 -disableddash {} -disabledfill {} -disabledstipple {} -disabledwidth 0.0 -joinstyle round -offset 0,0 -smooth 0 -splinesteps 12 -state {} -stipple {} -tags {wire4 connect wire Proc2 out sine in} -width 1.0 # now the block related variables\n set Proc1.bfunc { if ![catch {exec gcc -mno-cygwin -O -o [file rootname ${Proc1.in}] ${Proc1.in} -lm} r] {set Proc1.out [file rootname ${Proc1.in}]} { set Proc1.out $r } } set Proc1.bfunc_init {} set Proc1.in {sine.c} set Proc1.out {sine} set Proc2.bfunc {if ![catch [exec ${Proc2.in} ] r2] {set Proc2.out sine.ppm} {} } set Proc2.bfunc_init {} set Proc2.in {sine} set Proc2.out {sine.ppm} set sine.bfunc {set sine.out [sine read ${sine.in} ]} set sine.bfunc_init {} set sine.in {sine.ppm} set sine.out {}
/* File: sine.c */ /* combine transparent sine graphs in a ppm image */ /* Commercial rights reserved, Theo Verelst, free use otherwise, mentioning author */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #define PI 3.1415926535 unsigned char *im; int w,h; unsigned char * createim(int w, int h) { unsigned char *d; int x,y; d = malloc(3*w*h); for (x=0; x<w; x++) for (y=0; y<h; y++) { d[(y*w+x)*3+0] = 0; d[(y*w+x)*3+1] = 0; d[(y*w+x)*3+2] = 0; } return d; } int writeppm(char n[], int w, int h) { FILE *fp; fp = fopen(n,"wb"); if (fp == NULL ) return(-1); fprintf(fp,"P6\n%d %d\n255\n",w,h); fwrite(im, 3, w*h,fp); fclose(fp); return(0); } void rectangle(int x1, int y1, int x2, int y2) { int i,j; for (i=x1; i<x2; i++) for (j=y1; j<y2; j++) { im[(j*w+i)*3+0] = 0; im[(j*w+i)*3+1] = 0; im[(j*w+i)*3+2] = 0; } } #define point(mx,my,r,g,b) { im[((my)*w+(mx))*3+0] = (r); im[((my)*w+(mx))*3+1] = (g); im[((my)*w+(mx))*3+2] = (b); } #define point_trans(mx,my,r,g,b,a) { im[((my)*w+(mx))*3+0] += (r/a); im[((my)*w+(mx))*3+1] += (g/a); im[((my)*w+(mx))*3+2] += (b/a); } #define fpoint(x,y,r,g,b) im[(((int) (0.5+y))*w+((int) (0.5+x)))*3+0] = (r); im[(((int) (0.5+y))*w+((int) (0.5+x)))*3+1] = (g); im[(((int) (0.5+y))*w+((int) (0.5+x)))*3+2] = (b); void drawsine() { int i,j,k,l, max; double d,e,x; max = 4; for (x=0; x<2*PI; x+=PI/256) { for (k=1; k<=max; k++) for (j=h; j>(256-sin(((double) k)*x)*256); j--) point_trans((int) (0.5 + (x*512.0)/(2.0*PI)), j, 0, 255, 255, max); } } int main() { w = 512; h = 512; im = createim(w,h); if (im == NULL) { printf("Malloc error: quiting.\n"); exit(-1); } rectangle(0,0,w,h); drawsine(); writeppm("sine.ppm", w,h); return 0; }
The idea of the C program is that a PPM formatted image (large but uncompressed 'true' colour) is written to a fixed file, of which the size can be set by globals h and w, which gets an image by a bar representation of a number (max in drawsine) sine waves of increasing frequency, alpha blended together.
The procedure blocks can be inspected for their *.bfunc associated variables by right clicking the procedure (yellow) block and choosing 'data' from the popup menu. In essence, proc1 compiles the C program, and proc2 runs it, while the image block (called sine) updates the image it shows on the canvas by (re-) reading the sine.ppm image file when it 'fires'.
Bwise lets you fire a block by 'eval' from the popup menu (either the middle or right mouse button), while 'funprop' fires all out-->in connected blocks in sequence as well. So one can also by hand 'eval' each block, and make sure its output value is 'transfer'-ed to the next before executing the next, because the wires do carry information, not just triggers. which in this case is the base name of the source file 'sine'. You may want to have versions for that (sine2), or possibly only for a part of the network. See the in and out pins of the procedures, and their variable content verifiable and editable in the data windows.
The block function for the program execution is simplest: it simply execs the sine(.exe) program we just compiled, basically by :
exec sine
Except we take the name of the program from the input 'in' pin. The return value of the sine program is ignored (though it is in global variable r2 if we would want it), but the exec is wrapped in a catch to determine whether the program execution was succesfull or in error, for instance when we didn't 'cd' (in small or big console, or maybe you want to hardcode it in the bwise or cansine.tcl files) in tcl to the directory which contains the program file. The output value, stored in the variable associated with the 'out' pin is set to empty list when there is an error, or the the fixed expected sine.ppm filename which should contain the intended image after the C based sine program has completed successfully. So the whole block function, stored in the global variable Proc2.bfunc is (see the cansine.tcl file above, at the bottom):
if {![catch [ exec ${Proc2.in} ] r2 ]} { set Proc2.out sine.ppm } { }
The proc1 block function look a bit complicated at first (depending on who's looking), because I wanted it to do some automatic things straightforwardly. In essence it does:
exec gcc -o sine sine.c -lm
Which is the cygnus or linux command to compile the C program, and creating a program file called sine. Maybe for windows you may want to call it sine.exe, on XP (SP-1) with bash 2.05b.0(9) gcc 3.2, exe isn't needed, except you may want to use it in contrast with a linux executable. The whole command for the above version is:
if ![catch {exec gcc -O -o [file rootname ${Proc1.in}] ${Proc1.in} -lm} r] { set Proc1.out [file rootname ${Proc1.in}] } { set Proc1.out $r }
The compile command is caught for errors and called with the Proc1.in variables' content as C source file, while the output file is the base name of that filename, that is without .c . When there is no error, the name of the executable is put on the Proc1.out pin, otherwise the return value (probably the error) from the compiler.
The final block called 'sine' has a simple to type, completely tcl function, that is no exec, and no conditions:
set sine.out [ sine read ${sine.in} ]
The output pin will normally contain an emtpy list, which is fine because as it is it is the last block in the net. The Proc2.in pin variable contains the name of the image file (sine.ppm). The block assumes the existence of a Tk image called 'sine', which is called by name and given the subcommand 'read' with the image name as argument to update the image shown on top of the block.
Normally one could use
newimage sine.ppm
to get the same block, and possibly add more arguments for instance to put the block in place, but we couldn't create an empty block on the canvas. Here, the canvas was saved, which in Bwise saves the image blocks, too, and the saved file adapted slightly such that it assumes an empty image of size 512x512 in the block, also when the sine.ppm file doesn't yet exist.
TV: Who decided I wanted a certain ansi C variation rather than the original I put on ?
See also Binary image compression challenge - Theo Verelst's entry