C Code Problem (2) - Page 1
Forum section: electronics / general

author: Digibin
author_rank: Regular Contributor
timestamp: February 01, 2015, 05:13:12 pm
content: Hey. I'm working on a project involving a LCD screen and an ATTiny48, and decided to write a sort of splash screen to display on the LCD before moving into the main program code. Just for fun/practice. This follows from a previous post I made here:https://www.eevblog.com/forum/microcontrollers/c-code-problem/Before I started this afternoon the code worked fine and as expected, but after writing a few lines for my splash screen, it breaks. The splash displays as expected but once it moves onto the main program code it doesn't quite function properly. The .c file is attached, and the snippet that I've added and that appears to break it is this:Code:/* Boot splash screen */lcd_init(LCD_DISP_ON);// Initialize LCD to 4-bit modelcd_clrscr();// Clear displayconst char bat = " Bat ";// Splash screen text, top line//const char box = " Box ";// Splash screen text, bottom linechar buffer;// Buffer string to manipulate bat and boxint8_t i = 3;int8_t j = 4;for (int8_t h=4; h>=0; h--){memset(buffer, '\0', sizeof(buffer));// Reset/erase buffer string//strncpy(buffer, bat, 8);// Copy bat into buffer for manipulationbuffer = '<';// Set '<' in buffer stringfor (int8_t k=i-1; k>=0; k--){buffer = ' ';// And set all preceding elements to ' '}buffer = '>';// Set '>' in buffer stringfor (int8_t k=j+1; k<=7; k++){buffer = ' ';// And set all proceeding elements to ' '}lcd_gotoxy(0,0);// Go to first linelcd_puts(buffer);// Put string to display_delay_ms(1000);lcd_clrscr();// Clear displayi--;// Decrement to move to next stage of splash screenj++;// Increment to move to next stage of splash screen}Basically this just displays '<' and '>' moving from the middle of the LCD to the sides with the text 'Bat' appearing in this middle as they move outwards. This works fine but once it moves onto the main program code somehow my variable 'enc_count' is being incremented over and over until it reaches the upper limit that I set for it. This should be increment/decrement when I turn a rotary encoder, but somehow the program is doing it by itself.I've narrowed it down to the strncpy line. If I remove that line the program code works as normal (but of course the splash screen doesn't really work). Based on what I learned from the previous thread linked to above, is my use of strncpy perhaps causing stack overflow or something?As with sprintf is it better not to use strcpy in my application (i.e. with limited memory in my ATTiny48)?Any help or ideas would be appreciated.Cheers.

author: helius
author_rank: Super Contributor
timestamp: February 01, 2015, 05:20:44 pm
content: Code:const char bat = " Bat ";This is incorrect, your string constant is length 9 and needs an array like bat to hold it.Strings in C are null-terminated, that means that the last element of the string is a character of value 0.

author: Digibin
author_rank: Regular Contributor
timestamp: February 01, 2015, 05:38:55 pm
content: Good point, corrected that, but the same thing happens!

author: Maxlor
author_rank: Frequent Contributor
timestamp: February 01, 2015, 05:40:32 pm
content: Yup, lack of null termination is the problem here, and this line is probably where it goes wrong:lcd_puts(buffer);lcd_puts doesn't know when the string will end, and will keep reading and reading until it finds a '\0' somewhere in memory.You don't have to use null-terminated strings btw, it's perfectly fine to keep track of your string length some other way, such as compile time constants. You just can't use function expecting null-terminated strings if you go that route.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 01, 2015, 05:44:32 pm
content: Quote from: graymp89 on February 01, 2015, 05:38:55 pmDid you increase buffer to char?As Maxlor says passing a improperly terminated string to a routine which expects a nul terminated one is not going to go well.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 01, 2015, 05:51:05 pm
content: Another problem with the code is in the looping.The loop controlled by "h" iterates 5 times with values 4,3,2,1 and 0i starts out as 3 and you decrement each time round the loop, so on the last iteration it will be -1 - there is no buffer[-1] so the lineCode:buffer = '<';Will scribble on random memory.Similarly you write past the other end of the buffer array as well when j is 8 on the last interation

author: Digibin
author_rank: Regular Contributor
timestamp: February 01, 2015, 09:39:54 pm
content: Unfortunately that doesn't fix it, I hoped it would! Yes I've changed the buffer length to  and reduced the for loop iterations to h=3 (four iterations):Code:const char bat = " Bat ";// Splash screen text, top line//const char box = " Box ";// Splash screen text, bottom linechar buffer;// Buffer string to manipulate bat and boxint8_t i = 3;int8_t j = 4;for (int8_t h=3; h>=0; h--){memset(buffer, '\0', sizeof(buffer));// Reset/erase buffer stringstrncpy(buffer, bat, 8);// Copy bat into buffer for manipulationbuffer = '<';// Set '<' in buffer stringfor (int8_t k=i-1; k>=0; k--){buffer = ' ';// And set all preceding elements to ' '}buffer = '>';// Set '>' in buffer stringfor (int8_t k=j+1; k<=7; k++){buffer = ' ';// And set all proceeding elements to ' '}lcd_gotoxy(0,0);// Go to first linelcd_puts(buffer);// Put string to display_delay_ms(1000);lcd_clrscr();// Clear displayi--;// Decrement to move to next stage of splash screenj++;// Increment to move to next stage of splash screen}I still think that strcpy is doing something here. If I comment out that line it all works just fine. How much memory does strcpy occupy when called? I had a problem before when trying to use sprintf in that it consumed too much memory and caused problems.

author: nctnico
author_rank: Super Contributor
timestamp: February 01, 2015, 09:47:56 pm
content: I'd replaceconst char bat = " Bat ";with:const char bat[] = " Bat ";In that case you never have to worry about setting the length. The same goes for buffer. Just make it longer than necessary to avoid buffer overruns.

author: Jeroen3
author_rank: Super Contributor
timestamp: February 01, 2015, 09:51:27 pm
content: Quote from: graymp89 on February 01, 2015, 09:39:54 pmCode:char *strcpy(char *s1, const char *s2){char *s = s1;while ((*s++ = *s2++) != 0);return (s1);}Probably only the stackframe, since there are only so few variables they stay in the registers.It will however eat all your memory if it never finds 0.

author: helius
author_rank: Super Contributor
timestamp: February 01, 2015, 10:30:36 pm
content: Quote from: graymp89 on February 01, 2015, 09:39:54 pmDo you know that int8_t is a signed type? A signed 8-bit integer has a range [-128..127]. An unsigned 8-bit integer has a range [0..255]. If it's unsigned, then k>=0 is always true.Also, in terms of style, what do you need h for? It seems to me you could simplify your code tofor (int8_t i=3, j=4; i>=0; i--, j++)

author: grumpydoc
author_rank: Super Contributor
timestamp: February 01, 2015, 11:05:32 pm
content: QuoteThere's no real advantage to using int8_t anyway.QuoteThe whole thing seems quite fragile with lots of small loops to copy various bits into the string - change the length of the message and it will all fall apartWhy not do it with one loop to build the string and one to "move" the '<>' and try to make everything work automatically should someone come along and change the message.E.g.Code:static const char bat[] = " Bat ";// Splash screen text, top linechar buffer[sizeof(bat)];// Buffer string to manipulate bat and boxint l = strlen(bat);int i, h;for (h=1; h <= l/2; h++) {for (i=0; i < l; i++){if (i < l/2-h || i > l/2+h-1) {buffer = ' ';} else if (i == l/2-h) {buffer = '<';} else if (i == l/2+h-1) {buffer = '>';} else {buffer = bat;}}buffer = '\0';lcd_gotoxy(0,0);// Go to first linelcd_puts(buffer);// Put string to display_delay_ms(1000);lcd_clrscr();// Clear display}EDIT: Oh, BTW I padded out your modified copy into something that would run on a linux box and ran valgrind over it - seemed OK, so you might have a problem in some code you're not showing us.

author: eneuro
author_rank: Super Contributor
timestamp: February 01, 2015, 11:07:27 pm
content: Probably, the quickest way will be... clear those few lines of code and start from the beginingQuoteThan add a few loops and let C preprocesor evaluate buffer_len and bat_len for you (less bytes of output code probably ) and always ensure you have null (0) terminated strings

author: grumpydoc
author_rank: Super Contributor
timestamp: February 01, 2015, 11:23:56 pm
content: Not only is this not neededCode:bat[bat_len-1]= '\0'; //Not needed- bat should be already null(0) terminated when declared like above...but as you saidCode:const char bat[] = "Bat";the first line is actually invalid (write to read-only string)

author: nctnico
author_rank: Super Contributor
timestamp: February 01, 2015, 11:28:47 pm
content: Quote from: grumpydoc on February 01, 2015, 11:23:56 pmNope. You can initialise constants! Any decent toolchain (compiler et al) will put the initilisation value into program space (=flash on a microcontroller).

author: IanB
author_rank: Super Contributor
timestamp: February 02, 2015, 01:01:09 am
content: Quote from: grumpydoc on February 01, 2015, 11:23:56 pmDoubling up on nctnico, that = is not an assignment it is an initialization. Assignment and initialization are different.

author: helius
author_rank: Super Contributor
timestamp: February 02, 2015, 01:05:04 am
content: You sometimes have to read the whole postit is assigned in a subsequent line

author: grumpydoc
author_rank: Super Contributor
timestamp: February 02, 2015, 07:20:40 am
content: QuoteYes, of course you can initialise constants - they'd be sod all use if not.But you can't write to them - which is what you didlater, it wasn't the initialisation I was worried about - as helios says read the wole post; the code writes to a "const" string.

author: eneuro
author_rank: Super Contributor
timestamp: February 02, 2015, 07:54:55 am
content: Quote from: nctnico on February 01, 2015, 11:28:47 pmYep.It was defined as global constant in C program and compiled usingavr-gccQuoteIt is at the end of AVR ATTiny flash program in output binary:Quote

author: paf
author_rank: Regular Contributor
timestamp: February 02, 2015, 09:29:17 am
content: Grumpy mode on:Are you compiling with all the warnings turned on?That is, using the -Wall command line flag of (avr-)gcc?If you are not using the warnings or you are not looking at the warnings, the problem is not a programming problem.You really like pain and suffering.Grumpy mode off.C is a very powerful language, and like all powerful tools, should be used with extreme care.The compiler warnings are one of the easiest/fastest ways of getting feedback about our source code.

author: eneuro
author_rank: Super Contributor
timestamp: February 02, 2015, 11:05:56 am
content: Quote from: paf on February 02, 2015, 09:29:17 amCommon, quite usefull AVR program it is only afew lines of code and in many cases main concern is output code size and its speed, so anyway if you want to ensure that MPu will do what you want you have to look into assembler listing and see if everything is fineAcademic discussions about coding style doesn't matter-the only thig what matters is output MPU assembler code.

author: donotdespisethesnake
author_rank: Super Contributor
timestamp: February 02, 2015, 07:23:04 pm
content: The AVR GCC behaviour is rather non-standard, because the AVR has a Harvard architecture. Constants are copied from Flash to RAM to allow data pointers to work correctly. However, writing to string literals is illegal in C, and a bad programing habit, although you can get away with it on some implementations.If you try the same thing on most other CPUs, you will get incorrect results and possibly a bus fault. This exact same bug occurred recently on another forum where a user was porting from AVR to ARM and wondering why it didn't work.

author: dannyf
author_rank: Super Contributor
timestamp: February 02, 2015, 07:47:50 pm
content: QuoteWhat would be a few examples of such CPUs?

author: eneuro
author_rank: Super Contributor
timestamp: February 02, 2015, 08:12:25 pm
content: Quote from: grumpydoc on February 02, 2015, 07:20:40 amAny problem to define it like this if we want introduce better programming habbits?QuoteIt is not a point and problem was probably in not null '\0' terminated strings there, so it doesn't matter how such string is declared-if we want change it, yep it is better show that it might change somewhere in the code, so forget aboutconstand make itstatic...BTW: More important to use '\0' rather that 0x00 asnullstring terminator if we want code more portable

author: grumpydoc
author_rank: Super Contributor
timestamp: February 02, 2015, 09:15:18 pm
content: QuoteAnything with an MMU should be able to mark pages read-only and fault if written to if the MMU is being used.RISC architectures tend not to like mis-aligned reads/writes, eg trying to read a 32bit word from an odd address and will generate a bus fault if you try - SPARC, MIPS, ARM < v6 all do thisI'm fairly sure the various x86 multimedia instruction sets want data to be aligned even though normal loads and stores on x86 don't.ARM >= v6 will do unaligned reads in hardware, but as with all archictures whichcanit is less efficient to make use of this feature.

author: Jeroen3
author_rank: Super Contributor
timestamp: February 02, 2015, 09:23:46 pm
content: Static has no effect on the generated code for global scope variables.For global names it makes use of the object outside of the current module (c file) illegal.However, if you use static in a block or function (that is between '{' and '}'), static gives the object a persistent memory allocation, while keeping it only accessible from the scope it was declared in. It is persistent, that means it is still the last value the next time you enter the block. You do not initialize static types inside a block.Writing char bat[] = "bat"; is legal in any scope. If you have global strings that you'll be using outside main scope (from interrupt for example) you must make them volatile. That way the compiler always forces read-modify-write order for that object instead of assuming it's value for the sake of optimization.declaration: char bat;assignment: bat = 'a';initialization: char bat[] = "text"; char bat2[] = { 1, 2, 3, 4 };

author: helius
author_rank: Super Contributor
timestamp: February 03, 2015, 04:07:24 am
content: Quote from: eneuro on February 02, 2015, 08:12:25 pmthe 'static' storage class is one of the worst-named features of C. as another poster said, it means two completely different things depending on the scope of the identifier. please don't treat it as an "alternative" to 'const'Also, '\0' and 0 are the same according to the specification.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 03, 2015, 08:07:07 am
content: QuoteNodBut it is, in the end, just a keyword in the language and it is pretty simple to learn what it means.

author: helius
author_rank: Super Contributor
timestamp: February 03, 2015, 08:19:39 am
content: That's all true, but the way those keywords control visibility and external linkage is in my opinion too prone to error and not very powerful. Ada, Modula 3, and Oz got it right.

author: eneuro
author_rank: Super Contributor
timestamp: February 03, 2015, 08:35:52 am
content: Quote from: helius on February 03, 2015, 04:07:24 amProbably you should read one of classic C language books first and have longer programming experience.There are many architectures and then one day you will find out why better use '\0' sometimes

author: grumpydoc
author_rank: Super Contributor
timestamp: February 03, 2015, 10:03:03 am
content: QuoteOz is a new one on me but a quick look at Wikipedia suggests it was developed in an academic environment, Ada and Modula 3 are, of course huge designed-by-committee languages.C on the other hand, was originally part of the pet project of a couple of researchers on an old 16 bit system. It's not that surprising some things were rough and ready but it is a testament to the basic soundness and (more important) utility of the language that we are discussing it almost 50 years later.

author: DJohn
author_rank: Regular Contributor
timestamp: February 03, 2015, 11:50:54 am
content: Quote from: eneuro on February 03, 2015, 08:35:52 amOr we could just read the specification. '\0' is a constant of type int, value 0 (it's not a char). 0 is also constant of type int, value 0. Helius is right - they're the same. If you've got a compiler/target combination where they're different, you should complain to whoever sold it to you.

author: dannyf
author_rank: Super Contributor
timestamp: February 03, 2015, 11:51:24 am
content: QuoteLet me restate the question you just answered: on what CPUs would the use of "static char bat[]= "Bat";" cause bus fault?

author: grumpydoc
author_rank: Super Contributor
timestamp: February 03, 2015, 12:16:30 pm
content: QuoteAsked like that - none, I hope, since that is an initialisation and as I said earlier if you couldn't initialise a const variable they would be pointless.In any case I thought the question was about assignment, not initialisation, specifically writing to a character location within a "const" string.IFyou saidCode:const char bat[] = "Bat";and later triedCode:bat[sizeof(bat)-1]= '\0';As was suggested in an earlier post then that would be invalid code, and what happens is a bit system dependant, however, typically in a system using paged virtual memory where the compiler places the constant bat in a read-only page you will get a page fault.Smaller systems without MMU might generate a bus fault, it's logical enough to do so with an invalid write but it's as much system dependant as CPU dependant and I suspect most such systems simply ignore the write.

author: dannyf
author_rank: Super Contributor
timestamp: February 03, 2015, 12:23:11 pm
content: QuoteThink about the difference between "const unsigned char *" and "* const unsigned char".Many of the embedded compilers are wrong when it comes to "const" implementation, for good reasons overall.

author: eneuro
author_rank: Super Contributor
timestamp: February 03, 2015, 12:34:13 pm
content: Quote from: DJohn on February 03, 2015, 11:50:54 amI do not have to complain while I know what I'm doingHow will you explain that something which should be the same doesn't fit into your theory andsizeof('\0') != sizeof(0)... different in C++?

author: grumpydoc
author_rank: Super Contributor
timestamp: February 03, 2015, 12:40:17 pm
content: QuoteBut now you are shifting the goalposts somewhat are you not?If you sayCode:const char bat[] = "Bat";then the values in the array are "const" & the array name itself is not an lvalue.If you sayCode:const unsigned char *bat = "Bat";ORCode:unsigned char * const ball = "Ball";Then1) you now have two things, a pointer and a string literal2) the type of the literal differs in Ansi C (char[]) vs C++ (const char[])3) what is "const" differs - in the first example the content is immutable (pointer to const char)in the second the pointer is immutable (const pointer to char).I used to be good at this language lawyer type stuff but as I no longer code for a living I'm getting slightly rusty

author: DJohn
author_rank: Regular Contributor
timestamp: February 03, 2015, 01:15:58 pm
content: Quote from: eneuro on February 03, 2015, 12:34:13 pmBut we weren't talking about C++ were we? The thread is about C, and you explicitly mentioned "classic C language books".In C++, yes '\0' is of type char. It still has value 0, though, and if buffer is an array of chars,Code:buffer = '\0';andCode:buffer = 0;have identical effect. You're free to consider the first to be better style, but there is no observable difference in behaviour.Quote from: dannyfI no longer have a devkit on my desk to test it, but from memory on Playstation 3 that would result in bat being initialised as a pointer to some data in the .rodata segment, which is read-only. Attempting to modify it later would cause a bus fault.

author: dannyf
author_rank: Super Contributor
timestamp: February 03, 2015, 01:19:20 pm
content: QuoteNo. I thought the original discussion was over when you said you couldn't find a cpu where the said sentence would create a bus fault - the assertion you had earlier.My attempt was just trying to provide you with a better understand of what that initialization was really meant.

author: grumpydoc
author_rank: Super Contributor
timestamp: February 03, 2015, 01:36:14 pm
content: Quote from: dannyf on February 03, 2015, 01:19:20 pmNo, I did not make that assertion - donotdespisethesnake didIdiddescribe faults that might occur from writing to read only memoryandcircumstances where I might expect a bus fault - slightly differentQuoteMy understanding of initialisation vs assignment and the constness of variables is just fine, thanks.

author: dannyf
author_rank: Super Contributor
timestamp: February 03, 2015, 01:37:40 pm
content: QuoteYou think it is fine and it is fine are two separate things,

author: Digibin
author_rank: Regular Contributor
timestamp: February 03, 2015, 09:21:30 pm
content: Quote from: grumpydoc on February 01, 2015, 11:05:32 pmDoes it not save memory by using a single byte rather than the two that int uses?Quote from: grumpydoc on February 01, 2015, 11:05:32 pmThis works perfectly, thank you! I did think to try and do it like this somehow but couldn't figure out how to actually implement it. Looks easy now I've seen it.So back to my original point - the strcpy function somehow broke my code. No idea how, no compiler warnings or anything. Still interested to know why, if anyone has any more ideas.

author: IanB
author_rank: Super Contributor
timestamp: February 03, 2015, 09:41:55 pm
content: Quote from: graymp89 on February 03, 2015, 09:21:30 pmYou wrote strcpy above, yet you used strncpy in your original code sample.If you simply do this, does it work?Code:const char bat[] = " Bat ";char buffer[] = " Bat "; /* one way to make buffer same size as bat */...strcpy(buffer, bat);...lcd_puts(buffer);...

author: grumpydoc
author_rank: Super Contributor
timestamp: February 03, 2015, 09:59:48 pm
content: QuoteSmiley or not I'm afraid the only way I read that is as an insult. Exactly what do you think I misunderstand?Would you care to give us the benefit of your vast intellect in the form of an explanation rather than snide comments?Quote from: graymp89 on February 03, 2015, 09:21:30 pmAh, going back to your original post I see you are on an ATtiny48 so there probably is some merit in sticking to 8 bit types if you don't need bigger.

author: westfw
author_rank: Super Contributor
timestamp: February 04, 2015, 08:39:01 am
content: QuoteIn particular, some compiler setups for chips with von Neumann architectures use "const" as the "put this variable in flash instead of RAM" control, and subsequent attempts to write to them will result in illegal memory access traps. (I'm pretty sure that gcc for ARM is one example. Probably controlled by the linker maps.)

author: BloodyCactus
author_rank: Frequent Contributor
timestamp: February 04, 2015, 05:54:43 pm
content: I remember one compiler would put const strings/data into the .text segment and non const strings/data into the .data section. Another compiler would put ALL the things into .text, broke a lot of old style C that assumed strings were mutable and things using strtok would break all over the show, then gcc showed up and you had the "-fwritable-strings" option...

author: Jeroen3
author_rank: Super Contributor
timestamp: February 04, 2015, 07:24:27 pm
content: Quote from: westfw on February 04, 2015, 08:39:01 amThat is because you just described these rules:- I am allowed to change bat.- I shall not change *bat.But, since you made the rules, you can break the rules. Welcome to C.Otherwise you can use: "static const char * const" which forbids about everything I can think of now.Easier (not better) would be to make your constants using a #define. Since the compiler is somewhat smart, I should notice that all the literals placed by the preprocessor are identical, and thus only allocate the memory once. If run with the correct options of course.Writing to literals is never allowed, for obvious reasons.

author: DJohn
author_rank: Regular Contributor
timestamp: February 05, 2015, 10:47:31 am
content: Just a thought: this is AVR, and AVR is Harvard, and you use different instructions to access RAM and ROM. If the string is ending up in ROM, strncpy won't be able to see it. It's been long enough since I last used one that I can't remember exactly what happens in this situation.If you copy bytes manually from bat to buffer, instead of using strncpy, do you get "Bat" on the screen? If you get garbage, this will be why.I've got a project at home somewhere that does similar things. If I remember tonight, I'll dig it up and see what tricks it had to use.Code:char bat[] = "12345678";char buffer;strncpy( buffer, bat, 8 );This will copy the first 8 bytes from bat to buffer, then stop. It won't append a zero, because strncpy is a nasty little function that loves to surprise you. If youCode:buffer = 0;yourself, then all should be good. But your memset is doing that, so I don't know what's going wrong.

author: DJohn
author_rank: Regular Contributor
timestamp: February 05, 2015, 06:11:10 pm
content: Quote from: DJohn on February 05, 2015, 10:47:31 amNo, it looks like string literals (and other data) get copied to RAM unless you specifically request that they stay in ROM. Whatever the problem is, it's not that.

author: Jeroen3
author_rank: Super Contributor
timestamp: February 05, 2015, 08:26:26 pm
content: Quote from: DJohn on February 05, 2015, 10:47:31 amI do remember something with pgmspace instrinsics, does that still apply?http://www.nongnu.org/avr-libc/user-manual/pgmspace.html