C pointer confusion on char arrays - Page 1
Forum section: electronics / beginners

author: Veketti
author_rank: Regular Contributor
timestamp: December 02, 2023, 08:20:47 pm
content: Dear All,I'm bit confused on warnings regarding pointers and char arrays. When calling function and having "&" for the char array, it gives error "incompatible pointer type. If I leave this ampersand out, no warning. So should it be left out for char arrays?Second issue is that in "strlen(*text)" it somehow treats that as signed, even though it is declared as unsigned. Why is that and how to overcome this so that it is treated as unsigned?Here's the code (warnings added as a comment to corresponding line):Code:unsigned char text = "Hello";int main(void){ManipulateString(&text); // Warning: incompatible pointer type}void ManipulateString(unsigned char* text[]){// do somethinguint8_t LengthOfString,i;LengthOfString = strlen(*text); // warning: pointer targets in passing argument 1 of 'strlen' differ in signednessfor (i=LengthOfString ; i < 16 ; i++){if (i == LengthOfString) {*text = 'p';}else if (i == LengthOfString+1){*text = 'p';}else if (i == LengthOfString+2){*text = 'm';}else *text = 32;}}And last, I'm bit unsure is it even needed to declare char as unsigned when there are characters? Character ascii codes are all positive values so if it is not declared as unsigned I only get characters from 0 to 127?I have a feeling that I don't need to declare char as unsigned and also no need to use ampersand on that function...Thank you in advance.Edit: incorrect topic name. Was C# when it should have been C

author: MarkT
author_rank: Frequent Contributor
timestamp: December 02, 2023, 08:31:27 pm
content: You other confusion is between C# and C++ I think!In C and C++ an array is implemented as a pointer, so you can pass a char[] as a char* for instance.Code:void fun(char * param){....}void foo(){char array;fun(array); // pass as pointerfun(&array); // exactly the same thingfun(&array); // wrong, as this has type char**}And of course char is not the same as unsigned char, so they are different, incompatible types, so thus their pointer types are also incompatible.strlen returns size_t, not uint8_t, so respect that. Whether size_t is signed or not is beyond your control I believe.

author: u666sa
author_rank: Frequent Contributor
timestamp: December 02, 2023, 08:33:28 pm
content: I don't know C# so this advice take with a grain of salt, C++ programmer giving a C# programmer advice.Code:int main() {ManipulateString(static_cast<unsigned char*>(&text));}Option two is to use #pragma warn precompiler directives. You remove said warning before you call that function, and add it back up once you call it. Either way it is on crutches. Your original function should look something like this instead:Code:string& ManipulateString(string& text) {return string;}Instead of unsafe pointers, you should use build in class string, take a reference, manipulate reference, and return reference.

author: IanB
author_rank: Super Contributor
timestamp: December 02, 2023, 09:17:42 pm
content: Quote from: Veketti on December 02, 2023, 08:20:47 pmI think you are getting yourself confused because you have not adequately read and understood a book or a primer on the C language and especially how arrays and pointers work. Going through "The C Programming Language" by K&R chapter by chapter would always be a good start.In particular, C treats arrays and pointers as equivalent. So you should proceed as below.There are various other issues with your code sample that I do not have time to go into. Others here may help, and you will learn with time and practice.Code:#include <string.h>#include <stdio.h>static char text = "hello";size_t ManipulateString(char text[]);int main(void){size_t length;length = ManipulateString(text);printf("Length of string is %d", length);}size_t ManipulateString(char text[]){size_t length;length = strlen(text);/* do other things here *//* return final length of string */return length;}

author: magic
author_rank: Super Contributor
timestamp: December 02, 2023, 09:40:56 pm
content: As suggested above, you probably want "char text[]" or "char *text". The type you declared is "(a pointer to) an array of pointers to char" rather than "a pointer to an array of char". This type is commonly used for "argv" in "main", for example, which is an array of command line arguments supplied to the program, but it would be awkward and unusual to store a single text in such manner.Secondly, whole thing could be rewritten as:Code:text = 'p';text[length+1] = 'p';text[length+2] = 'm';for (i = length+3; i < 16; i++)text = ' ';Assuming that enough space is guaranteed to exist. If length+2 can be greater than 15 then it's obviously not going to work.And the last line could probably use memset instead, but memset's "count" argument is size_t which isunsignedand therefore passing a negative number (by naively writing 16-(length+3) when length is almost 16 already) would result in a lot of random memory being overwritten. A corsesponding signed type is ssize_t but I think it's only defined in POSIX (i.e. Unix, Linux, OSX, etc).

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 02, 2023, 09:50:11 pm
content: With buffer overrun protection:Code:char text_hi = "Hello"; // Avoid calling a global variable same as function argumentsManipulateString(text_hi, sizeof(text_hi)); // array name without index makes a pointer.void ManipulateString(char * text, uint8_t size){char *ppm = "ppm "; // ppm stringchar *s = text; // temporal pointer variable to use for own strlenuint8_t len = 0;while(*s++) len++; // This is basically strlen. A C string is always NULL ended (after "ppm " there's an additional byte set to 0) so we simply need to count until we read 0.while(*ppm && (len < (size-1))){ // Copy ppm string into buffer, but only while it fits. Subtract one, reserved for NULL termination.text[len++] = *ppm++;}text = 0; // ALWAYS finish the string (NULL character) or you'll run into HUGE issues, most functions rely on this null char to detect string end.}

author: IanB
author_rank: Super Contributor
timestamp: December 02, 2023, 09:54:31 pm
content: DavidAlfa originally highlighted an important thing above, which is that you should always pass in the length of a buffer to a function, so the function knows the size of the thing it has to work with. Furthermore, the function should always check that the length is not exceeded, to avoid buffer overruns.(DavidAlfa's originally quoted code was incomplete, but it might be fixed by now, or it might not have the length of the buffer as a function argument anymore.)

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 02, 2023, 10:10:45 pm
content: Yeah, like always I messed up at some point and corrected it later. I was posting both examples, decided to clean it up and keep only the proper code... pasted part of old code (Copy & Paste task failed) and noticed when it was gone.I must be the member with the Guinness World Records in edited messages.

author: radiolistener
author_rank: Super Contributor
timestamp: December 02, 2023, 11:29:41 pm
content: Quote from: Veketti on December 02, 2023, 08:20:47 pmarray name is a pointer. So variabletextin your code has typeunsigned char*. When you use&textit returnsunsigned char**, but your function expectsunsigned char*which is different type, this is why there is error...This works exactly the same for C, C++ and C#. C# has a little different syntax for array definition, but array variable still can be used as a pointer, the same as in C and C++. For local array you can pass array as pointer directly, but if array is declared non local you're needs to pin the memory allocated for array because it is allocated in managed memory and can be moved during defragmentation, in such case pointer for non pinned array will become invalid and accessing it can cause access violation exception or can corrupt process memory.

author: ejeffrey
author_rank: Super Contributor
timestamp: December 02, 2023, 11:39:26 pm
content: Quote from: radiolistener on December 02, 2023, 11:29:41 pmNope. Read more carefully.First, while arrays automatically convert to pointers at function calls, they are not pointers. So &text has the type char *. Confusingly if you pass text to a function C implicitly takes the address of the first element and is also type char *. So, when passing function arguments text and &text are exactly the same thing.Instead of you look closely the function is actually expecting (char **) but it is spelled in the equivalent (char *text[]).

author: IanB
author_rank: Super Contributor
timestamp: December 02, 2023, 11:45:57 pm
content: Quote from: ejeffrey on December 02, 2023, 11:39:26 pmThis is not correct. While it is true that arrays are not pointers, the consequences go much deeper.text is defined as an array of char of length 30, so &text is a pointer to an array of char of length 30. The type of &text is actually char (*).Code:char text = "hello";typedef char (*Array30ptr); /* typedef improves readability for complex type declarations */Array30ptr p = &text;(*p) = 'a';On the other hand, &text is a pointer to char, so you could write:Code:char text = "hello";char *q = &text;q = 'b';

author: IanB
author_rank: Super Contributor
timestamp: December 03, 2023, 01:43:36 am
content: Quote from: radiolistener on December 02, 2023, 11:29:41 pmI don't know why you listed C# in there. C# is a different language entirely.QuoteAnd here you are just being silly. If you try to mess around with pointers like this, you are not writing C# anymore, and your code will be rejected by all competent reviewers. What you wrote has some technical truth to it, but it is not relevant to people learning C# as a programming language.The only use for raw pointers and pinned memory in C# is for interop with native code, but this does not apply to people who are staying within the .NET CLR (as they should be when doing normal programming).

author: Veketti
author_rank: Regular Contributor
timestamp: December 03, 2023, 10:30:55 am
content: Thank you all. Especially DavidAlfa, your example code works wonderfully. I previously received occasionally some groovy values, so I must have had some buffer overrun happening. I added loop to fill remaining space up to 16 characters with spaces to clear all the ghost characters from display (16x2 character display) if shown shorter value on display than previous value. Hope this doesn’t render the buffer overrun protection inoperable? Also changed the chars to unsigned to have the full 0-255 ascii codes available:Code:void ManipulateString(unsigned char * text, uint8_t size){unsigned char *ppm = "ppm"; // ppm stringunsigned char *s = text; // temporary pointer variable to use for own strlenuint8_t len = 0;while(*s++) len++; // This is basically strlen. A C string is always NULL ended (after "ppm " there's an additional byte set to 0) so we simply need to count until we read 0.while(*ppm && (len < (size-1))) // Copy ppm string into buffer, but only while it fits. Subtract one, reserved for NULL termination.{text[len++] = *ppm++;}while(len < 17){text[len++] = ' '; // fill spaces to empty screen ghost characters from previous longer values}text = 0;}What I’m basically trying to make is CO meter for my home to monitor that I don’t close the flue damper too early on my wood burning fireplace. Eventually I’m planning to make many of these with zigbee or similar wireless capability to have unit on multiple rooms and then one display to monitor the values in bedrooms etc. However so far my STM32G050F6P6 with 32kB of memory is already >89% full and it is just showing the value on display and instruction. If I originally knew these STM32 need lots of memory for HAL I’d go with higher memory unit. I’ve got some G070CBT6 with 128kB of flash so hopefully that is enough.. I’m not that capable to do the programming without HAL so I guess I don’t have other choice. In contrast with PIC16F1847 I never ran out of its 14kB memory and had much more complicated code.For the CO module I’m using Winsen ZE07-CO –module. One thing I’d also like to ask, this is bit off from the original pointer topic, but hope it’s ok. Datasheet of that unit tells to get the concentration value you need to: Gas concentration value = (High Byte*256+Low Byte) x 0.1. I found one example where this was done by:Code:value = (float)((float)(answer_data<<8 | answer_data))/10;Bitwise shift is clear, but could someone explain the bitwise OR |? Datasheet is telling to add the low byte but in this example used bitwise OR operator. Obviously it works, but I just don’t understand why.Thank you very much!

author: Veketti
author_rank: Regular Contributor
timestamp: December 03, 2023, 10:43:56 am
content: Picture of the current breadboard setup. On background "real" residential CO meter. Its minimum value that it shows is 10ppm so I think there is some extra marginal compared to this "higher end" ZE-07..

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 03, 2023, 01:21:14 pm
content: Quote from: Veketti on December 03, 2023, 10:30:55 amAre you enabling compiler optimizations for that STM32G050F6P6? Ensure to set it to Os (Optimize for size).Quote from: Veketti on December 03, 2023, 10:30:55 am1In this case it's the same result, you shifted the value 8 bits to the left, so the lower byte is empty, nothing there to cause a carry.Ex.:1000 + 1=10011000 | 1 = 1001.

author: Veketti
author_rank: Regular Contributor
timestamp: December 03, 2023, 02:17:06 pm
content: Quote from: DavidAlfa on December 03, 2023, 01:21:14 pmOh my. Didn't know about that. Enabled it and now it is 68%. Wohoo. Makes me wonder why it isn't enabled as default?Quote from: DavidAlfa on December 03, 2023, 01:21:14 pmIndeed. I couldn't get my head around it that 11 | 01 is still three not four, but the shift made the differences.Sorry to ask again, but was it so that the added space filling didn't mess that buffer overrun protection?

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 03, 2023, 02:37:22 pm
content: Because it might cause problems if you don't code well. Mainly in interrupts and such.Check where your space is going in the build analyzer.

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 03, 2023, 02:42:30 pm
content: Code:while(len < 17 && (len < (size-1))) // This ensures no buffer overrun happens{text[len++] = ' '; // fill spaces to empty screen ghost characters from previous longer values}But anyways, if you know the buffer is larger than 17, and any string you append won't surpass it, it can be omitted.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 03:10:34 pm
content: Quote from: IanB on December 03, 2023, 01:43:36 amPointers is a part of C# language and there is nothing wrong with it. It allows to write clean and fast code, especially when you're doing marshaling with native code. It is unsafe, because it is allows to do direct memory access, but if you're know what you're doing you can use it to write clean and fast code. The same you're needs to know what you're doing when you dealing with C/C++ pointers. But with C# it is a little bit more complicated because managed memory can be defragmented, so it needs to be careful when you're passing pointer to a managed memory to unmanaged code.For example I wrote game engine with using direct OpenGL/OpenAL calls in pure C# and it works great, surprisingly more fast and more stable than existing engines written in C/C++. And it don't needs to recompile, I can run the same executable on Windows, Linux and macOS with no issue. It will be impossible with no using pointers.

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 03:21:58 pm
content: Quote from: radiolistener on December 03, 2023, 03:10:34 pmIt's not because "you can do it" and "it works" that it's necessarily a good ideaunsafe operation in C# are the exception and are normally never used. If your code is full of it, you are not using C# correctly or you are using the wrong programming language for what you are trying to achieve.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 03:32:14 pm
content: Quote from: Kosmic on December 03, 2023, 03:21:58 pmI'm writing in C# from 2004 and worked on a very large enterprise projects. So, don't tell me what I'm using correctly and what is not... At about 2006 I also had thoughts like your due to lack of knowledge... but since then a lot of water has passed under the bridge and I learned a lot of new things and saw a lot of different code.Unsafe code is just a thing which needs to be used properly. This is true that this is complicated part and novice cannot use it properly. But if you try to avoid it where it is required you will get bullshit code which works slow and hard to read.

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 03:37:08 pm
content: Quote from: radiolistener on December 03, 2023, 03:32:14 pmHehe, I have a master degree and more than 20 years exp as a professional programmer mostly in C++ but also C and C#. Trust me I don't lack knowledge

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 03:54:56 pm
content: Quote from: radiolistener on December 03, 2023, 03:32:14 pmWhy not simply using C++ then ?

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 04:13:06 pm
content: Quote from: Kosmic on December 03, 2023, 03:37:08 pmif you wanted to impress me with this, your attempt is failed. I got master degree with honors in Computer Science (software development of automation systems) 20 years ago, before that I also get bachelor degree in computer electronics. I wrote first C program at about 1992, and learned C++ at about 1994. Most of the time I worked as software development engineer in medical devices industry for a company which was in a top 5 of fortune 500 list.As you probably understand this is much more strict than usual programming, because it requires additional knowledge of software development lifecycle and medical standards certification (ISO13485 etc) with regular examination to be allowed to work on medical devices projects. If you know requirements for medical devices and software systems used in cardiology surgery and cardiopulmonary resuscitation, then you can understand why it uses so strong requirements for development process and this is what I worked on. And what?

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 04:26:35 pm
content: Quote from: Kosmic on December 03, 2023, 03:54:56 pmC++ is more unsafe than C# with unsafe code, it's more hard to debug and perform code analysis. Technically yes, you can do all things in C++ more effective, but in practice it will be more hard than in C#, because it will be more easy to do some mistake in C++ and more hard to find/detect it.

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 04:28:19 pm
content: Quote from: radiolistener on December 03, 2023, 04:13:06 pmNo, I have no need to impress anyone. I'm far passed that point in my career. I was trying to answer your "lack of knowledge" assumption.Automatic memory management is core to C# design. By completely bypassing it all the time you can't really say you are using the language correctly. If you need or want to manually manage memory that much you should use something else. Like C++. Would be a lot more efficient.

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 04:33:18 pm
content: Quote from: radiolistener on December 03, 2023, 04:26:35 pmIf you know the language I don't think so. It's probably because I know C++ pretty well, but I think your unsafe C# is more dangerous.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 04:36:53 pm
content: Quote from: Kosmic on December 03, 2023, 04:28:19 pmI don't bypassing it, I just use it more effective with using unsafe code. Of course there is no needs to use unsafe code for a usual code, it is required when you're needs to do proper marshaling and time critical code optimizations. Most of the code in a project don't requires it, but it don't means that this is useless feature which don't needs to be used at all.

author: langwadt
author_rank: Super Contributor
timestamp: December 03, 2023, 04:41:48 pm
content: Quote from: Kosmic on December 03, 2023, 04:33:18 pmhow's being "unsafe" some of the time going to be worse than being "unsafe" all the time ?

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 04:42:14 pm
content: Quote from: radiolistener on December 03, 2023, 04:36:53 pmThen we are saying the same thingIt's used for special cases but most of the code is not in unsafe block.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 04:43:06 pm
content: Quote from: Kosmic on December 03, 2023, 04:33:18 pmprobably you're didn't worked on a large projects which include C# and C++ projects together.Can you show some example where C# code is more dangerous than in C++ on your opinion?

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 03, 2023, 04:44:34 pm
content: Not again, you both create your own damn thread and fight there!

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 04:48:35 pm
content: Quote from: radiolistener on December 03, 2023, 04:43:06 pmNo i don't have good C# exemples. When I was working on a C# project we were avoiding unsafe code as much as possible. That was our guidelines. So we never ended up having exemple of dangerous unsafe C# code.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 04:50:04 pm
content: Quote from: Kosmic on December 03, 2023, 04:42:14 pmit depends on the module. Some modules requires unsafe code almost everywhere. Some modules don't requires it at all.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 05:01:33 pm
content: Quote from: Kosmic on December 03, 2023, 04:48:35 pmsome guidelines are broken by design and can lead to cloning of the same errors. I was seen it...You're needs to understand how it works, when and why to use one or another solution by context, not by guidelines...Do you implementing finalizer for a class which implements IDisposable, but don't holds native resources?

author: ArdWar
author_rank: Frequent Contributor
timestamp: December 03, 2023, 05:02:04 pm
content: Quote from: Veketti on December 03, 2023, 10:30:55 am(s)printf(I assume you use it somewhere) uses alotof space especially if you actually print floats ("%f"). Even using something as silly asCode:printf("%d.%d", (int)val, (int)fmod(val*10.0), 10));can save quite a lot depending on your compiler and stdio implementation it uses.Maybe also try usingMCU optimized librariesand see if it helps.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 05:16:48 pm
content: Quote from: ArdWar on December 03, 2023, 05:02:04 pmI remember that iar printf library code for MSP430 has some issue which can lead to hung with some specific formatting and yes, it eats a lot of space. And if you look at library code for format parsing, it has a bunch of useless code which is almost never used. So, I'm agree - it's really better to use simplified printf format and don't use library with full printf support. This saves a ton of memory and makes the code noticeably faster

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 05:44:17 pm
content: Quote from: radiolistener on December 03, 2023, 05:01:33 pmI don't think our guidelines was necessarily wrong considering our base C# programmer was struggling understanding references and implications of capturing references in a lambda.In general C# teach programmers to let go of memory management so when they suddenly have to manage it in a unsafe section, the risk of problem increase drastically.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 06:47:40 pm
content: Quote from: Kosmic on December 03, 2023, 05:44:17 pmI don't agree, on the contrary, C# requires to understand memory management more deep in order to properly work with it, so when you going to unmanaged code it will be even more easy because all memory is assumed as unmanaged and there is no needs to take care about possible issues related to background memory defragmentation. And C# learns to write code more clean and deterministic. Also C# compiler has many checks and more strict syntax so it teaches you not to make stupid mistakes which can remains in C++ code for a long time unnoticed.There are really some difference which may lead to mistakes when switching from C# to C++, but this is just a matter of habit.But in general, writing code in different languages learn you to write code more clean and make less mistakes. So, switching from C# to C++ and back learns you to write code more clean and allows to notice hidden issues.

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 06:55:37 pm
content: Quote from: radiolistener on December 03, 2023, 06:47:40 pmOk, how is this possible when the memory is managed ? Normally it's all automated and you don't take care of it. By definition, C# doesn't require you to understand everything about memory management. In C/C++ you don't really have the choice to learn since it's not done automatically for you.I understand you might be really proficient at managing your memory with unsafe code in C# but I really doubt this is the case for your average C# programmer that never did any C/C++.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 07:05:41 pm
content: Quote from: Kosmic on December 03, 2023, 06:55:37 pmit is automated, but you're needs to understand how it works and sometime how to control it under the hood in order to use it more efficiently and don't make hidden bugs. Even if you don't use unsafe code at all, you're still needs to understand it to avoid unexpected performance degradation.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 07:12:35 pm
content: Quote from: Kosmic on December 03, 2023, 06:55:37 pmYes, unfortunately there are a lot of people who writing in C# and don't understand what is the pointer at all. But this is a side effect of possibility to write pure safe code in C#. This is not good and such peoples often make stupid mistakes related to memory management even in safe code.It seems that some peoples just unable to understand what is the pointer, this is curious but I found that for some reason this is just impossible to explain them it. They still can write code, but when it's going to pointers they just unable to understand it. It sounds like joke, but such issue really exists and I have no idea how it is possible, probably this is some kind of genetic predisposition

author: Kosmic
author_rank: Super Contributor
timestamp: December 03, 2023, 07:24:40 pm
content: I agree with your points. What I'm trying to say is because it's automated, peoples get lazy and they tend to don't understand how it's working under the hood. and that can lead to really dangerous situation when they start playing with unsafe code.I'm generalizing here but this is what I observed anyway.

author: radiolistener
author_rank: Super Contributor
timestamp: December 03, 2023, 07:45:43 pm
content: Quote from: Kosmic on December 03, 2023, 07:24:40 pmI think this is happens when people just using ready to use libraries, and when they needs to implement something they looking for existing library that can do it and just call it instead of implementing required functions. But if you're writing your own libraries to communicate with devices and OS, it always needs to learn how it works under the hood.There is side effect of such lazy coding. Most of ready to use libraries come with limitations, for example it cannot run on some platform or incompatible with old OS, or require some payment, or sending unwanted telemetry, etc. So, if you use such approach, your code will be bloated with unexpected functions and will not be portable, so your app will be able to run on some specific OS with specific libraries only.