Welcome, Guest. Please login or register.

Author Topic: Executing c command in c  (Read 10357 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline olsen

Re: Executing c command in c
« on: May 18, 2017, 09:42:26 AM »
Quote from: foleyjo;825901
Also what are APTRs and BPTRS, Should I be using them or is it fine to stick with Object * ?


Careful there, this is going to hurt.

You will meet BPTRs almost exclusively in the dos.library functions and the bookeeping data structures which it manages. If a function returns a BPTR, store it as a BPTR, never as an Object pointer or anything else. If a function requires a BPTR as parameter, make sure it came from a variable of type BPTR. Some other operating system functions, which pass parameters to dos.library, also use BPTRs, as well as storing them in the data structures they work with.

You should treat BPTRs as different from any other regular 'C' pointer type you might be familiar with. It helps to keep those variables which hold BPTRs separate from the rest, and always have them use the BPTR type when you declare them.

Why the fuss? A BPTR is not a regular 'C' pointer (the name is an abbreviation for "BCPL pointer", with "BCPL" being the programming language in which dos.library was originally written). You might picture BPTRs as something special which only dos.library knows how to deal with, and the remainder of the operating system has no clue about. If you mix BPTRs up with regular 'C' pointers strange things are guaranteed to happen: crashes are just the beginning, misfortune of all kind will follow you.

You already met the oddly-named APTR (I believe the name may be an abbreviation for "address pointer", but further back in Amiga history, it might have actually stood for "Amiga pointer"). This one is a proper, regular 'C' pointer which holds a memory address. It's just window-dressing for 'void *', which means that it is a pointer which can be assigned to any type of other regular 'C' pointer without causing the 'C' compiler to to warn you that the assignment mixes data types which do not match.
 

Offline olsen

Re: Executing c command in c
« Reply #1 on: May 18, 2017, 10:51:50 AM »
Quote from: foleyjo;825906
So if I understand you right....
I don't need to worry about BPTR unless I'm using a predefined function that takes or returns a BPTR (which will usually be dos functions such as the Lock one I'm using)


Yes, in a nutshell. There always has to be a very good reason for wanting to know more about what a BPTR is and what exactly it points to. You usually do not need to know about these details.

Quote
APTRs I assume it's more of a preference thing. So I could use char * or APTR when declaring a string pointer and not expect much difference.


There is more to the APTR type, and how/when to use it.

While the APTR can be assigned to any other pointer type without the 'C' compiler bothering you with warnings, there is a cost: you willl, many times in your 'C' programming career, end up using this special "keep it quiet" propery of the 'void *' pointer (which is what the APTR is) unwisely.

My advice is to never, ever use the APTR or 'void *' pointer in your code if you can help it, and if you do need to use it, you should feel guilty about doing so ;)

The use of this pointer type all too easily obscures the fact that the data pointed to may not be compatible. The compiler will warn you about that, and this is not something you should take lightly by shutting up the warning through an APTR or 'void *' pointer type. What is generally safer is to get rid of that warning by having the pointers being assigned or compared be of compatible types so that the similarity of the data comes out of the data structures as known to the 'C' compiler.

While you do have the choice to use an APTR or 'void *' pointer type in place of more specific pointer types, such as 'char *', it will pay off if you stick to the pointer type which best describes the data being referenced by it. The compiler will help you along by checking for you if the data types are sufficiently similar. You could do all this by yourself, but at some point (programs grow and evolve until you cannot realistically keep track of each part, and every problematic piece of code) this becomes a tedious exercise. Let the machine do that work for you :)

Side-note: the APTR also exists because of the need to let programmers develop software for the Amiga operating system in assembly language. The data structures you find in 'C' header files for the operating system have counterparts in 68k assembly language header files. Those 68k assembly language header files build the same data structures as found in the corresponding 'C' language header files through the use of macros. In these assembly language macros, "APTR" is a macro which stands for a 32 bit word, which is a pointer of some sort.
 

Offline olsen

Re: Executing c command in c
« Reply #2 on: May 18, 2017, 12:50:18 PM »
Quote from: foleyjo;825912
Thanks Olsen,
I understand what they are now.
I decided against using APTR for the reasons you said. I'm making too many rookie mistakes at the moment and luckily the compiler is catching them. If it didn't I'd have lots more problems.


Funny that you mention lots of problems... One thing which I did not know when I started out in 'C' programming, and which initially gave me cause to worry, was the sheer number of complaints the 'C' compiler would throw at me.

You could make one small mistake, and the compiler would be animated to report errors for code which was perfectly sound.

The trick to deal with this situation is to work through the error messages from the top, one at a time. Ignore the bulk of error messages at the tail end of the compiler output for now.

Why does this happen? The compiler may stumble over code which derails its normal processing operations (could be as little as one character in the wrong place), and in an attempt to get back on track, it skips as much code it cannot make sense of until it looks like it may be safe to process the remainder. This attempt may derail things again, mind you, and then more error messages tumble out.
 

Offline olsen

Re: Executing c command in c
« Reply #3 on: May 26, 2017, 12:25:24 PM »
Quote from: foleyjo;826251
With the extern command does it matter that I'm not declaring fp globally or passing it between functions?
Hm... does "fp" have to be a global variable?

I am asking because there are several different ways to shoot yourself in the foot here.

It could be that you have a "FILE * fp;" defined inside a function (a local variable, as opposed to a global variable), which shares the same variable name with the globally defined "fp". This is the kind of thing which will inevitably make you miserable because everything seems to look right, yet the value of the variable seems to change. It does not change, the code is merely preferring the value stored in the local variable to the one in the global variable by the same name.

My advice would be to keep the names of local and global variables completely separate so that you know just by looking at what you code does that it's either a global or a local variable which is being used. If you don't yet have a good idea how to name your variables (this is one of the hard practical problems in computer science, I kid you not), you could start by changing the global "fp" variable name to "global_fp", for example and recompile your code. Does it compile? What needs to be changed to make it compile again?

I strongly recommend that you choose longer, more meaningful names for your variables. Introductions to the 'C' programming language tend to stick to the "no idle chatter" practice, keeping the names of functions and variables short. Variables are typically one letter each, maybe two, or even three if the author is generous.

When I started out writing 'C' code, I found that with short variables names, I had less to type, but more to remember. As a programmer, you already have a lot to remember to begin with, starting with how the individual parts of your program work together. Trying to remember all the names and respective purposes of variables does not help, it just makes your work harder. Worse: with similarly-named two-letter variables a mere typo can stress you out even more.

If you must use a global variable, make sure that you can know whether the contents of that variable are sound or not. If the fopen() function succeeds, it will return a special type of pointer ("FILE", as defined in the "stdio.h" header file) which is not NULL. If you store that pointer in a global variable and later close that file, make sure that you reset the global variable to NULL. That way you can more easily find improper use in your own code which references that global variable after the file has already been closed.
 

Offline olsen

Re: Executing c command in c
« Reply #4 on: May 26, 2017, 01:19:49 PM »
Quote from: foleyjo;826257
Think I've confused everyone with my explanation.

fp is declared locally. So I either have (just a quick example so ignore typos etc)

...

In Example 1 the if(fp) returns TRUE.
In Example 2 the if(fp) returns FALSE


If fopen() fails, there will be an error code set which explains why it did not work. That error code will be found in the global 'errno' variable (which is declared in the "stdio.h" header file), but you could use the perror() function (also declared in the "stdio.h" header file) to print an error message:

Code: [Select]

int
function_name(void)
{
    FILE * output_file;
    int success = FALSE;

    output_file = fopen("output","w");
    if(output_file != NULL)
    {
       ...

       fclose(output_file);

       success = TRUE;
    }
    else
    {
       perror("fopen failed");
    }

    return(success);
}


Opening a file for writing can only work out if that file is not currently being used, such as for reading or for writing. Protecting a file from deletion or writing can also prevent it from being opened for writing.

Also, if the name of the file you want to overwrite actually refers to a directory, you won't be able to open it for writing either.

If you succeed in opening a file for writing, make sure that you close that file when you no longer need it, or the next attempt to open it for writing may fail, even though your own program opened it.
 

Offline olsen

Re: Executing c command in c
« Reply #5 on: May 30, 2017, 02:38:04 PM »
Quote from: foleyjo;826408
Had a typo there. List_items->max_number is my char *. Should have been a , after it.
I think it'll be easier to make it an int inside the structure. I had problems. Not sure why I did it as a char chat in the first place. Think I was getting a crash and changing it fixed things. Should have known it would come back to haunt me.
I'll check out snprintf too


You might want to look at your data structure definitions, pondering whether each member's name suggests both its purpose and its content.

If a structure member would be named "max_number", and its purpose is to show that number in text form, "max_number_label" might be a better name.

As short as member names should be, every name tells a story and suggests how you might use it. If the first glance gives an impression that doesn't fit well or may even be misleading, then you are bound to make mistakes. This might be less of a problem in short programs, but as you are building larger and larger programs, you should not need to rely upon memorizing structure names, their respective member names and their purposes.
 

Offline olsen

Re: Executing c command in c
« Reply #6 on: May 30, 2017, 03:25:01 PM »
Quote from: foleyjo;826417

Just been reading about snprintf and I had come across it before but there is lots of conflicting information.
sprintf,snprintf,strcpy and strncpy all seem to have people saying you should use them or you shouldn't use them.


Well, you need to know why each one would be a poor choice.

strcpy() and strncpy() are part of the basic 'C' language runtime library. They go way back when the programmers who would use them could be relied upon to be intimately aware of the limitations of these functions. Nowadays you should try to avoid them because neither function knows when to stop if it has to copy strings to a destination of limited size.

If you use strcpy() to copy a 16 character string to a string buffer which can only hold 4 characters, strcpy() doesn't know that it should stop after the 3rd character.

The strncpy() function is a special version of strcpy() in that it will copy only a fixed number of characters, but it might not add a NUL termination. There are few reasons for using strncpy() in the first place. You will probably find that the conditions under which strncpy() makes sense apply only a handful of times during your 'C' programming career.

sprintf() has the same problem as strcpy(): it will put text into the output string buffer without regard for that buffer's size. Again, if sprintf() wants to put 16 characters into the output string buffer, and that buffer is just 4 characters in size, sprintf() will not stop after the 3rd character.

What you should be using instead of strcpy() is a function call strlcpy(). strlcpy() can be told how many characters (including the NUL) will fit into the output string buffer. It will stop copying if the output buffer turned out to be too short. Better still, strlcpy() will always make sure that the output string buffer is properly terminated by a NUL. There's one exception: if you tell strlcpy() that the output buffer is 0 characters in size it will not copy anything.

In case you were wondering, there is a strlcat() function which should be used rather than strcat(), too.

Instead of using sprintf(), use snprintf(). It's the same deal as with strcpy() vs. strlcpy(): snprintf() can be told how many characters will go into the output string buffer. If the output buffer turns out to be too short to hold everything snprintf() might want to store there, it will stop before overrunning the output string buffer.

Quote

I tend to malloc my destination string to the size of the source string and then add a null terminator


If that's all that needs to be done here, you might want to try strdup() instead. This will allocate memory for the NUL-terminated string, copy the original string over and return the copied string. It's a standard 'C' runtime library function.
 

Offline olsen

Re: Executing c command in c
« Reply #7 on: June 01, 2017, 09:43:40 AM »
Quote from: foleyjo;826545
Ok I solved my issue for this post.I had a do while instead of a while do which was causing silly things to happen. My 2nd question I still don't know so leaving it below
Which leads me to a curiosity I have.
If calloc is used to assign memory for an array and a string is an array of characters, why is calloc not used for strings (at least not in the examples I can find)


The 'C' runtime library contains several functions which deal with memory management. Each serves a specific purpose, although it is hard to say today why they all wound up in the library. There is malloc(), calloc(), realloc() and strdup(). Each of these functions may return a pointer to allocated memory which can be released with free(), or passed to realloc().

calloc() is one of the odd functions in this list. It will allocate memory for you, and if successful, will fill the allocated memory with zeroes. How you tell calloc() how memory is needed is rather peculiar: instead of asking for a certain number of bytes (like you would do with the malloc() and realloc() functions), calloc() wants to know how to break down the total allocation size into portions, and how many portions are required.

To answer your question: calloc() can be used to allocate memory for strings, but since calloc() does so much more than is needed if all you intend is to copy a NUL-terminated string into the buffer, malloc() does the same job just fine. malloc() is the simplest possible way to get this done.

Keep in mind that calloc() will fill the allocated memory with zeroes, and if you are going to copy the string into that memory, the zero-fill operation is not necessarily required. Also, calloc() wants you to break down the size into two parameters, this being a portion size and the number of portions required. malloc(), on the other hand, just needs to know the total size.
 

Offline olsen

Re: Executing c command in c
« Reply #8 on: June 01, 2017, 09:53:10 AM »
Quote from: foleyjo;826421
tried strdup() previously and just tried strlcpy().

For both I get no prototype function.
Guessing that relates to the compiler I'm using (Storm C 3.0) .


There's the rub. An older compiler will not support more modern library functions unless somebody takes the time to add them to it. You may be stuck with the state of the art at the time StormC 3.0 shipped.

Quote
So really I should be using snprintf in the places where I used strcpy.


Well, that depends upon what you need to accomplish. In my experience, the simplest possible tool that gets the job done tends to be the most appropriate.

snprintf() as well as sprintf() are mainly used for translating data (numbers, single characters, NUL-terminated strings) into text form.

If all you need is to copy one NUL-terminated string to a memory buffer, strcpy() may be a simpler solution. The drawback of strcpy() is that it does not respect output buffer boundaries, whereas snprintf(), if you have it, will do. In that case, snprintf() may be the safer alternative to strcpy() although it's purpose is not in copying strings, but in translating data into text form.

As a 'C' programmer you will have to make choices such as this (strcpy() vs. snprintf()) all the time. When the obvious solution would be the one you chose not to use, it's good practice to add a comment explaining briefly why the choice was made.

Quote

I actually used strcpy because the tutorial I read said I shouldn't use sprintf. I only started using sprintf when I was looking for a way to write an integer into a string and that's when I started reading the reverse argument that strcpy shouldn't be used. Both arguments talked of the snprintf and strncpy but again both claimed 1 should be used and the other was bad.

If the tutorial does not explain why one choice is better suited than the other, what can you do but ask us? :)
 

Offline olsen

Re: Executing c command in c
« Reply #9 on: June 02, 2017, 02:18:37 PM »
Quote from: Thorham;826576
Why not implement simple things like that yourself? I usually don't bother with using the C runtime for strings at all. It bloats the executable for a few functions that are all a few lines of C. Furthermore, your own functions can be tailored to your exact needs.

Writing your own string processing functions does make sense. The challenges you will be facing are, however, not going to be smaller than the original designers of the 'C' runtime library had to deal with ;)

You still need to define what your own functions should do (not that hard), and you still need to test your code and document it.

If you are only just starting out writing 'C' code, you might consider the testing & documentation portion to be not that important (I certainly thought that I had more important things to do, back in 1988). 'C' being what it is, testing and documenting your code are essential for writing robust, working code, rather than creating code which keeps surprising you by the many ways in which it causes crashes, or sucks up time for debugging. That's true for most programming effort, but 'C' makes it much easier to shoot yourself in the foot, and much easier to do much higher damage as a result.

The standard 'C' runtime library has its limitations and knock-on effects (not all of them good), but you can at least expect it to be well-documented and well-tested.

The trade-off is in either working within the constraints of the 'C' runtime library and modestly filling in the gaps you perceive with code of your own design, or completely relying upon code of your own design.

The greater freedom afforded by using your own design can be an illusion. Returning to the code you wrote a little time later you might just find that it is underdocumented and/or less robust than you thought it was when you wrote it.

Lack of documentation is certainly a challenge if you have to explain to others how it works, and why it works that way. Relying upon the 'C' runtime library instead of going your own way at least has the advantage that the documentation already exists and need not be fully explained by yourself to others.
 

Offline olsen

Re: Executing c command in c
« Reply #10 on: June 03, 2017, 08:12:38 AM »
Quote from: foleyjo;826602
Apart from the mui specific stuff (Not sure if my question on getting items from a sorted list was missed or if nobody had an answer) my main problems have been strings.
How to use MUI is a subject all by itself. Not everybody who's written Amiga software over the years has used it, or is even sufficiently familiar with it to explain how to get things done. There is more than one way to knit an Amiga user interface. MUI, by virtue of being a framework, is one of the most complex toolboxes you could use.

Quote
I'm used to languages where I don't have to worry about where they are in memory or remembering to have the end string characters. Now I have my head around those I'm getting the desired outputs and not getting any memory related errors.
Now you know why these other languages exist ;)

Quote
It's nice to learn a different way of looking at them. I always knew they were arrays of characters but never thought too much about what the functions were actually doing.
The Amiga, and even the minicomputers, workstations, etc. which came before it, did not have a lot of memory or CPU power to spare. The programs running on these machines had to be very much in control of how memory was spent. There used to be a time when 2 Megabytes of RAM was quite a lot, and 512 Kilobytes was what you had to plan for, because most of your users likely had exactly this much available.

Languages such as Python, JavaScript or even Perl have to perform the same low level memory management operations for strings (among other data structures). The programs running in these interpreters don't have to bother, but the price is in that more memory is needed and more CPU power is needed.
 

Offline olsen

Re: Executing c command in c
« Reply #11 on: June 03, 2017, 08:44:45 AM »
Quote from: nyteschayde;826613
My question is how does one execute arbitrary binary from within one's program.


At the most basic level, it boils down to knowing where the first instruction of the code to execute is located in, assign that address to a function pointer (if you are using 'C') and calling the code through the function pointer. There are no mechanisms present in the Amiga operating system which deny execution of arbitrary code. It's one shared address space for everything, and no sections are specifically restricted to either executable or non-executable content (on the 68030 and beyond some portions of the address space will be marked as cacheable or not cacheable, though).

Things changed a bit with the introduction of the 68030, and a bit more with the 68040, though. If the code you wanted to execute had to be pushed into memory, unpacked or auto-generated, you better made sure that the CPU data cache was flushed and the contents committed to memory (pipeline synchronization included). Sometimes things still worked out if you didn't know that you had to flush the cache and synchronize the pipelines, but you could not rely on being lucky all the time.

That's the basics. It gets more complicated if you need to load code into memory.

The easier case involves position-independent code, which references data, subroutines, etc. relative to the program counter (address of the instruction currently executing). With this code, you just reserve memory for it, copy the code into it and call it through the function pointer, etc. Yours truly once did that kind of thing for a certain Amiga hard disk controller which had to support booting from an FFS formatted partition. That was in the days when the FFS was not in ROM and had to be loaded from disk. However, the FFS at the time was one compact position independent chunk of code, so I stuck it into the hard disk driver's ROM and just ran it from there. Needless to say, don't try this at home, especially if you're 20 years old and under quite some time pressure ;)

The more difficult case requires that you load code from disk, or from ROM, without knowing beforehand where in memory exactly it will have to rest, this code not being position-independent. When you start a program from Workbench, the operating system will do exactly that: figure out how much memory is required to load the program, allocate memory for it, then load the program code and data into that memory. Finally, once everything is in place, the code and data are fixed up so that all the address references (data to data, code to code) match the respective memory addresses which code and data were loaded into (a process called "relocation", which is supported by the Amiga executable file format). The operating system provides all the tools to do this complicated business for you in dos.library, through the LoadSeg() function. If successful, LoadSeg() will return a special kind of pointer that refers to the first instruction of the code loaded (actually, it points to a 32 bit word in front of the first instruction). Again, you can use that code pointer with a 'C' function pointer and just invoke it to execute that code.

That's pretty much it, in a nutshell :)