|
|
View previous topic :: View next topic |
Author |
Message |
kWoody_uk
Joined: 29 Jan 2015 Posts: 47 Location: United Kingdom
|
Are function prototypes valid in CCS? [SOLVED] |
Posted: Thu Jun 02, 2016 9:44 am |
|
|
Hi all,
I'm creating a large project with multiple files, in the usual CCS approach of one main.c file which includes the other files in it.
The main thing is, where do I put the function prototypes for each of the other .c files? If I place them in separate .h files which are also included at the top of the main.c file, the compiler doesn't seem to be using them as the first reference: Instead, it is using the actual function declaration as the prototype and is seeing the prototype as some kind of overloaded function.
In simple terms.. How do I make the compiler see the Prototypes BEFORE it encounters the actual functions?
I'm using MPLAB if it makes any difference?
Hope this makes sense.
Thanks
Keith
Last edited by kWoody_uk on Fri Jun 03, 2016 5:36 am; edited 2 times in total |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Jun 02, 2016 3:54 pm |
|
|
Here is one way to do it. I edited the prototype statement for the
read_reg() function so it specifies an incorrect data type for the
parameter. I specified int16 when it should be int8. This causes the
compiler to give the error shown below. This proves that the compiler is
seeing the prototype:
Quote: | Executing: "C:\Program files\Picc\CCSC.exe" +FM "pcm_test.c" +DF +LY -T -A +M -Z +Y=9 +EA #__16F1847=TRUE
*** Error 112 "pcm_test.c" Line 14(1,1): Function used but not defined: ... read_reg 679 SCR=962
1 Errors, 0 Warnings.
Build Failed. |
If I edit the reg_rw.h file and change the parameter data type back to
'int8', then the error goes away.
Main C file:
Code: | #include <16F1847.H>
#fuses INTRC_IO, NOWDT, BROWNOUT, PUT
#use delay(clock=8M)
#include "reg_rw.h" // This file contains the function prototypes
//================================
void main()
{
int8 result;
write_reg(0, 0x55);
result = read_reg(0);
while(TRUE);
}
//-----------------------
#include "reg_rw.c" // This file contains the functions
|
reg_rw.h
Code: | #define I2C_SLAVE_ADDRESS 0xC0
int8 read_reg(int16 reg); // *** Deliberately used int16 to cause an error
void write_reg(int8 reg, int8 data);
|
reg_rw.c
Code: | #use i2c(master, I2C1)
//---------------------------------------
// Read a register in the sensor chip.
int8 read_reg(int8 reg)
{
int8 retval;
i2c_start();
i2c_write(I2C_SLAVE_ADDRESS);
i2c_write(reg);
i2c_start();
i2c_write(I2C_SLAVE_ADDRESS +1);
retval = i2c_read(0);
i2c_stop();
return(retval);
}
//---------------------------------------
// Write to a register in the sensor chip.
void write_reg(int8 reg, int8 data)
{
i2c_start();
i2c_write(I2C_SLAVE_ADDRESS);
i2c_write(reg);
i2c_write(data);
i2c_stop();
} |
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Fri Jun 03, 2016 1:28 am |
|
|
It's worth saying, that this is the 'old standard' way of doing things (and nothing wrong with that!...). So the .h contains the #defines and the prototypes, and the .c contains the corresponding code.
You can also potentially avoid double declarations, by using the preprocessor. So (using Pcm_Programmer's example) the .h file has:
Code: |
#ifndef __read_reg__
#define __read_reg__
#define I2C_SLAVE_ADDRESS 0xC0
int8 read_reg(int16 reg); // *** Deliberately used int16 to cause an error
void write_reg(int8 reg, int8 data);
#endif
|
and the .c file has:
Code: |
#include "reg_rw.h"
#use i2c(master, I2C1)
//---------------------------------------
// Read a register in the sensor chip.
int8 read_reg(int8 reg)
{
int8 retval;
i2c_start();
i2c_write(I2C_SLAVE_ADDRESS);
i2c_write(reg);
i2c_start();
i2c_write(I2C_SLAVE_ADDRESS +1);
retval = i2c_read(0);
i2c_stop();
return(retval);
}
//---------------------------------------
// Write to a register in the sensor chip.
void write_reg(int8 reg, int8 data)
{
i2c_start();
i2c_write(I2C_SLAVE_ADDRESS);
i2c_write(reg);
i2c_write(data);
i2c_stop();
}
|
Then you can include the prototypes 'early', by including the .h file, and later add the .c file, or if you want have both the prototypes and the code all loaded by just including the .c file. Also you can put extra 'includes' of the .h file, and it won't result in multiple definitions (the file will only actually load once). |
|
|
kWoody_uk
Joined: 29 Jan 2015 Posts: 47 Location: United Kingdom
|
|
Posted: Fri Jun 03, 2016 2:47 am |
|
|
Thanks for the advice. However, I have played around a bit and found that even in a single file, you can still circumvent the system...
If you do the following:-
Code: |
#include <18f46k22.h>
#fuses INTRC_IO, NOWDT, BROWNOUT, PUT
/* Tells Compiler the clock speed to allow DELAY commands */
#use delay(clock=64M)
/* Prototypes */
/* Oops, I forgot to update the prototype to remove the third parameter! */
void func1( int8 par1, int8 par2, int8 par3 );
void func2( void );
void main (void)
{
/* Main loop */
while ( TRUE ) {
func2();
}
}
/* Why is this not being flagged as an error? */
void func1( int8 par1, int16 par2 )
{
par1 = par2;
}
void func2( void )
{
func1( 3, 5 );
delay_cycles( 1 );
}
|
No error is reported. Is this because func1 declaration is implied when called from func2 and is just being seen as an overloaded function?
If this is the case then errors would not be flagged if you accidentally forget to change the function prototype, as I have in the above example.
I thought that if functions follow main, they must all have a forward declaration?
I eagerly await your thoughts
PS: V5_58
Thanks,
Keith |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Fri Jun 03, 2016 3:29 am |
|
|
It doesn't error, because you don't call func1.
In answer to your original question line, it is _not_ 'encountering' func1.
You can have any number of unused definitions. |
|
|
kWoody_uk
Joined: 29 Jan 2015 Posts: 47 Location: United Kingdom
|
|
Posted: Fri Jun 03, 2016 3:35 am |
|
|
Ahh.. but the code still runs without errors even if I comment out the func1 prototype... surely this isn't correct, especially as it's after the main function?
And I am using func1.. I'm calling it from func2. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Fri Jun 03, 2016 3:41 am |
|
|
Where you are calling it, is after it has been defined.
The point is that with overloading, it is perfectly legitimate to have multiple function definitions and prototypes. So long as they are before the function is encountered.
Now if you moved the func1 definition so it was after func2, then it'd complain that the function is used but not defined. |
|
|
kWoody_uk
Joined: 29 Jan 2015 Posts: 47 Location: United Kingdom
|
|
Posted: Fri Jun 03, 2016 3:47 am |
|
|
I've obviously got the wrong end of the stick then, especially regarding compilation.
My understanding was if all other functions were after the main they needed to have function prototypes.. is this NOT the case then? |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
|
Posted: Fri Jun 03, 2016 3:56 am |
|
|
Quote: |
I thought that if functions follow main, they must all have a forward declaration? |
No, they don't have to. In your example func1 doesn't need to have a prototype as it's only called by func2 that's defined later. A prototype for func2 is needed because it's called by main() before it's defined.
C doesn't have overloading... but actually CCS C does - it's a rare exception - probably as it's needed to implement some libraries in C, particularly maths routines, which can take different types for the parameters.
C's preprocessor, and the includes and the related prototyping mechanism is, language-ly speaking, unusual arguably to the point of being an aberration. The way C does function definitions and declarations has not been used in many other languages, even many C derived ones. Other languages just don't have any of it, particularly ones created relatively recently. C# has no preprocessor and no includes, for example. There is a reason for this: it's clunky, complex, confusing and prone to programmer error.
C has this idea of a declaration, often called a prototype. This just says, "expect this type of function". Then there is the definition, which is the actual body - the code - of the function.
There are two main reasons why prototypes are needed (other reasons are generally related to these) One is to allow what CCS C calls multiple compilation units, or the separately compiled units, linked as object files, of conventional C code.
C originally didn't have prototypes; they were bolted on later, probably to make implementation of larger systems more manageable, and was required early on in C's life for things like the Unix kernel. Arguably it's not required on PICs due to the much smaller size, and embedded nature of PIC code.
The second main reason is to allow recursive data structures such as linked lists. Such data structures are only really useful on processors with relatively large memory, from which a biggish chunk is used as a data heap. CCS C does have dynamic memory capability, but really it's not appropriate and is deprecated by most serious CCS C programmers. I never use it, and I doubt any regulars here do.
The other reason for using it is code organisation. The structure of header and C files is often considered the "conventional" way of coding C. It's the way many programmers expect C to be, but I suspect relatively few have ever considered why it's that way. But it doesn't have to be that way, and in many other languages would never be, and they don't have to have anything like C's preprocessor. Even C itself has largely gone away from relying on the preprocessor, for example constants are now explicitly defined in the language and don't need to be done by #define, a pre-processor facility.
A final reason, related to the code organisation thing above, is to provide a means of referring to intrinsic or built-in functions (which have no C representation, but are implemented by the compiler or programming environment), or libraries (which are often a case of multiple compilation units, i.e. they are linked as object files, not included as source files).
In my view, a big issue with the .h/.c model is that it requires two files to be maintained, .h an .c, and kept in step, rather than one. It requires text-based preprocessing, even if just by #include. It's all a bit, well, clunky and not well-thought through. There are many issues, such as the problems of multiple inclusion of the same file, which is normally got round by the "inclusion guard" shown above, implemented by the pre-processor.
Yes, it works, sort of, and many people expect it of C, demand it even, but it's not what's now considered good language design and implementation, as witnessed by the very few languages, essentially C and C++ only, that use it. All the rest don't have declarations separate from definitions.
In my own code, I don't use prototypes unless I specifically need them, which is almost never. That means my code tends to have few .h includes - I tend to only use them for target specific parameters and things like versioning information - and a many more .c files. My sub-functions come before my main, and so my main is at the end of my main file with little or nothing after it. For PIC sized projects it works well and has fewer pitfalls and requires fewer workarounds and relies less on C's historic peculiarities than the .h/.c "conventional" model.
I'm not exactly sure what's happening in this particular case. It may be that CCS's (unusual) overload capability is muddying things. The parameters in the prototypes must match both in type, order and number (it is not actually required to even name them in the prototype, and the names, if given, don't have to match). However, that's not really compatible, error detection wise, with the ability to overload functions.
Who knows, maybe it's not being flagged as the overall size of the parameters is the same in both cases: three bytes. The compiler should be flagging up something, ideally because the prototype has no matching definition. Func2 is happy because it can see a fully defined (?overload?) version of func1. And then, nothing tries to call a three parametered overload of func1, so the fact that one is not defined is not an issue. As would be the case in C++ that does have overloads.
C's type checking is weak, to say the least. You can get away with things in C that are simply not allowed in more recent languages, even C-derived ones. You can easily tie yourself in knots with dodgy implicit type conversions in C. For example in C, numeric literals, such as the 3 and 5 have int type by default, and will automatically be cast to whatever is defined for the parameter, so it will fit the defined version of func1. Have you tried three parameters, or a second parameter explicitly defined as int16? |
|
|
kWoody_uk
Joined: 29 Jan 2015 Posts: 47 Location: United Kingdom
|
|
Posted: Fri Jun 03, 2016 4:29 am |
|
|
Thanks for the comments so far guys; very interesting.
I know I'm labouring the point but why doesn't the following produce an error?:-
Code: | #include <18f46k22.h>
#fuses INTRC_IO, NOWDT, BROWNOUT, PUT
#use delay(clock=64M)
// Prototype
void func2( void );
void main (void)
{
/* Main loop */
while ( TRUE ) {
func2();
}
}
void func1( int8 par1, int16 par2 )
{
par1 = par2;
}
void func2( void )
{
func1( 3, 5 );
delay_cycles( 1 );
}
|
Am I right in thinking that the compiler runs through the code line-by-line, processes the main(), then comes across func1() which doesn't need a prototype as it hasn't been used yet, but is aware of it (deemed to have been declared), then processes func2() etc..
I know this is basic stuff but I'm pulling my hair out here as I've been coding for five or more years and my fundamental understandings are being challenged. It all started yesterday when I re-organised the code to attempt to make it more readable.
Keith |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
|
Posted: Fri Jun 03, 2016 5:23 am |
|
|
Quote: | why doesn't the following produce an error? |
Because it's perfectly correct C. As I said before, you have to have a prototype for func2 as you use it in main() before you define it. The prototype tells the compiler how to call func2, but obviously doesn't tell it what func2 does, that comes later when you define func2.
func1 is defined, in full, before you call it in func2. That's just fine, no need for a prototype - it's perfectly normal, correct C. However, you can give it a prototype, anywhere before you define it, if you like, that's also correct, but in this case rather unnecessary, C.
To reiterate, prototypes are only required if you call a function before you define it, or if the defintion is external to the calling unit, i.e. in a library or a linked object file. If you define a function before any calls, then no prototype is needed.
Last edited by RF_Developer on Fri Jun 03, 2016 5:45 am; edited 1 time in total |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1349
|
|
Posted: Fri Jun 03, 2016 5:29 am |
|
|
kWoody_uk wrote: | Thanks for the comments so far guys; very interesting.
Am I right in thinking that the compiler runs through the code line-by-line, processes the main(), then comes across func1() which doesn't need a prototype as it hasn't been used yet, but is aware of it (deemed to have been declared), then processes func2() etc..
Keith |
Somewhat correct yes! When the compiler comes across
Code: |
void func1( int8 par1, int16 par2 )
{
par1 = par2;
}
|
It implicitly has the prototype now which would be:
Code: |
void func1( int8 par1, int16 par2 );
|
The key here is that without an explicit prototype, the actual function definition is by default also the prototype. The function func2() had an explicit prototype near the top of the file, but the function func1() didn't have an explicit prototype, so the definition filled that role. |
|
|
kWoody_uk
Joined: 29 Jan 2015 Posts: 47 Location: United Kingdom
|
|
Posted: Fri Jun 03, 2016 5:34 am |
|
|
Ahhhhh, I see. Now it becomes clear! Sometimes, Google just doesn't cut it!
Thanks to all who replied to my probably fundamental questions.
Once again, I'm surprised and glad that busy and obviously experienced coders can give me the time to help me learn.
Keith |
|
|
RF_Developer
Joined: 07 Feb 2011 Posts: 839
|
|
Posted: Fri Jun 03, 2016 6:07 am |
|
|
Without prototypes, the lowest level functions have to be at the top of a C file, with successively higher and higher level routines following. The main would have to be at the end - the higest level function. That's how C was before prototypes were introduced. That's how many other languages are today. That's how I prefer to organise my C code.
Prototypes allow programmers to call functions before fully defining them, or to define them in separately compiler code: libraries etc. With prototypes, programmers are free to organise C code more or less any way they like. Many like to have prototypes for every routine up front, then comes the main, and after, often in no particular order come all the other called functions. It also allows code to be separated up into much more managable sections, often along functional or layered lines. It allows more than one programmer to be working on different parts of the code at once. It allows for object librairies. All good things if done well, but it also allows total disorganisation, with code here, there and everywhere, where its very hard to find where the actual work takes place.
For me, PIC code is small and compact enough to not require the use of separately compiled code units. While I make use of common code, especially for things which most people would use the term "drivers", I know full well that one size rarely fits all in the embedded world, and that any such drivers mey need to be cut differently due to the limitations of hardware. For example anything that communicates by SPI. There are countless "drivers", well they are example code really, for SPI bits and pieces, supplied by CCS. Great, but what happens when you want to use two or three of them? How do you manage working all of them through the one SPI interface, especially when each has different #use_spi lines? To sort that out you need to take the "drivers" and rework and integrate them into one system. Sure I can still use different .c files for each, but maybe now they talk through a single SPI driver layer .c include. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|