Typecasting pointer in C - Page 1
Forum section: electronics / general

author: nForce
author_rank: Frequent Contributor
timestamp: November 03, 2018, 01:30:33 pm
content: I have a basic question:Why are we typecasting pointers in C like this:Code:int b = 65;int *p = &b;char *c = (char *)p;and not as this:Code:char *c = (char) *p;Thanks.

author: sokoloff
author_rank: Super Contributor
timestamp: November 03, 2018, 01:38:22 pm
content: Quote from: nForce on November 03, 2018, 01:30:33 pm*p is "the thing that p points to". In this case, with p being an int*, *p is an int.Your second piece of code would be:Take *p (which is an int).Cast it to a char.Assign that char to char* c. (non-sensical)In your first code example, p is an int* again. Your assignment to c (a char*) is take the pointer-to-int p, cast it to a pointer-to-char, and assign that to c. That means that c points to some portion of b (because char is narrower than int on [almost?] every platform).

author: legacy
author_rank: Super Contributor
timestamp: November 03, 2018, 02:06:06 pm
content: look at this differenceon m68k, casting (uint8_t) in arithmetic operations forces the compiler to use 8bit-size operations ( in Motorola assembly are operations with the suffix ".b"), and this is relevant for how the ALU sets flags in the status register, specifically it's interesting for the overflow flag, which is handled in 2^(8-1) range, 2^(16-1) range, 2^(32-1) range, depending on suffix { ".b", ".w", ". l" }on MIPS32, casting (uint8_t) in arithmetic operations ... seems to be completely irrelevant, what really matters on my Atlas C compiler is if you cast signed or unsigned. MIPS has no sized operation, everything is supposed to be 32bit. Oh, I still have to check what happens on MIPS64 where the ALU is 64bit.The casting of pointers has the meaning of setting the operation sizee.g. you cast a pointer to 8bit, the alignment is 1byte, if you type pointer++, it's incremented by 1.e.g. you cast a pointer to 16bit, the alignment is 2byte, if you type pointer++, it's incremented by 2.e.g. you cast a pointer to 32bit, the alignment is 4byte, if you type pointer++, it's incremented by 4.Code:private void hAllo(){p_this_t p_this; /* void pointer */p_uint8_t p_08;p_uint16_t p_16;p_uint32_t p_32;char_t body[] = "0123456789abcdef";p_this = body;p_08 = p_this;p_16 = p_this;p_32 = p_this;p_08++;p_16++;p_32++;console_out_char( "08: ", (char_t) dereference(p_08), "\n" );console_out_char( "16: ", (char_t) dereference(p_16), "\n" );console_out_char( "32: ", (char_t) dereference(p_32), "\n" );}Code:OrangeCube pointers $ obj/hAllo08: 116: 232: 4

author: nctnico
author_rank: Super Contributor
timestamp: November 03, 2018, 03:30:51 pm
content: Quote from: nForce on November 03, 2018, 01:30:33 pmWhy? Because the programmer who wrote that clearly doesn't know how to write C. Do yourself a favour and never ever use typecasting.One of the problems you'll run into is that many architectures (ARM for example) don't allow to do unaligned access. This can lead to two problems: 1) the compiler ignores the alignment and you'll get hard to track down data corruption or the program crashes randomly. 2) The compiler sees the unaligned access and decides to produce code which accesses the data byte per byte making the program bloated and slow.PS: 'char *c = (char) *p' is different than 'char *c = (char *) p'. The first says to give pointer c the value of a character at the address p points to. If even it compiles then it is still wrong. The second says make pointer c point to whatever pointer p is pointing to and treat pointer p as being a pointer to a character.

author: legacy
author_rank: Super Contributor
timestamp: November 03, 2018, 04:13:11 pm
content: Quote from: nctnico on November 03, 2018, 03:30:51 pmyeah, the Atlas C89 compiler is a neat example of this for MIPS

author: ale500
author_rank: Frequent Contributor
timestamp: November 03, 2018, 04:19:30 pm
content: QuoteI'm pretty sure Cortex M3 cores allow it, while MIPS cores do not (there are special instructions for those), I think older ARMs didn't too.

author: joshtyler
author_rank: Contributor
timestamp: November 03, 2018, 04:25:19 pm
content: Quote from: nctnico on November 03, 2018, 03:30:51 pmI think this advice is a bit too strong. Typecasting in C does make it easy to get yourself into a mess (as your comment demonstrates), but sometimes you need typecast variables, and in some circumstances doing so is perfectly safe.For example, I would argue the following code is a perfectly safe and valid thing to write, despite the implicit typecast:Code:char c = 'a';putchar(c); //Note, putchar has signature: int putchar(int character)

author: nctnico
author_rank: Super Contributor
timestamp: November 03, 2018, 06:24:31 pm
content: Quote from: joshtyler on November 03, 2018, 04:25:19 pmThe problem with this particular example is that it is an inheritance from a past where safe coding wasn't very high on anyone's list and saving memory space had a higher priority. It leads to situations where the chare type just doesn't cut it. Another fine example of this duplicity is getchar() which returns an int. When the returned value is negative no character has been received.

author: helius
author_rank: Super Contributor
timestamp: November 03, 2018, 06:33:42 pm
content: I wouldn't call that duplicitous. It's the programmer's job to read and internalize his language manual instead of making assumptions.In C you don't have fancy exceptions and so the return value is used to signal success or failure.I don't know if I agree that "saving memory space had a higher priority in the past." In K&R C, all function arguments are either ints or doubles, you cannot even have a function taking only a char. The automatic widening inputchar(c)would have also been true for any function you could have defined to operate on characters.

author: nctnico
author_rank: Super Contributor
timestamp: November 03, 2018, 07:18:53 pm
content: Quote from: helius on November 03, 2018, 06:33:42 pmRemember that an int's actual size (=number of bits) depends on the architecture. On some (ancient) architectures an int may be an 8 bit signed.

author: joshtyler
author_rank: Contributor
timestamp: November 03, 2018, 07:39:52 pm
content: Even if you don't like that example, it is trivial to construct another one. Lets say we have an 8 bit UART, and want a program that sums all of the numbers received over that UART:Code:uint8_t read_from_uart(void);...uint16_t sum = 0;while(1){sum += read_from_uart(); // Typecast...// Do something useful here}Or maybe you're writing a desktop application and need some memory off the heap. You will need malloc, good luck using the output without casting void * to something else!Code:void * malloc(size_t size);I think that is impossible to avoid the fact that typecasting is inherent to the C language, and you are forced to do it sometimes. C doesn't give you a lot of safety nets, it is up to you as the programmer to build them in.

author: sokoloff
author_rank: Super Contributor
timestamp: November 03, 2018, 08:09:21 pm
content: Quote from: nctnico on November 03, 2018, 07:18:53 pmInts are guaranteed sinceat leastthe C89 language spec to span [-32767, 32767] (inclusive).In practice, that guarantees they're going to be at least 16 bits on a C compiler meeting the 1989 or later C spec.I'm too lazy to go chase down my 1978 copy of K&R to verify that it was spec'd the same back then as well.

author: sokoloff
author_rank: Super Contributor
timestamp: November 03, 2018, 08:15:33 pm
content: I found a scanned version of 1978 K&R online.It does notspecifythat ints must be at least that large, but rather leaves it as "the natural size of integers on the host machine".At that time, no implementation detailed in the table had fewer than 16 bits.Reference:https://archive.org/details/TheCProgrammingLanguageFirstEdition/page/n41

author: nctnico
author_rank: Super Contributor
timestamp: November 03, 2018, 08:46:27 pm
content: You've got to love that text: 'short is no longer than long'. Having types with basically unknown ranges cause lots of grief.

author: sokoloff
author_rank: Super Contributor
timestamp: November 03, 2018, 08:51:42 pm
content: Quote from: nctnico on November 03, 2018, 08:46:27 pmTotally agreed. That's why <limits.h> exists, but the programmers who are most likely to be bitten by overflow bugs (ones on the junior "half" of the experience curve) are the ones least likely to know about limits.h

author: glarsson
author_rank: Frequent Contributor
timestamp: November 03, 2018, 09:07:37 pm
content: Quote from: nctnico on November 03, 2018, 08:46:27 pmBut it made it possible to write efficient C compilers for machines with, today, odd word sizes (12, 18, 24, 36...). Also, it didn't prevent C from prosper and survive, outclassing many other languages.

author: nctnico
author_rank: Super Contributor
timestamp: November 03, 2018, 09:08:42 pm
content: Quote from: sokoloff on November 03, 2018, 08:51:42 pmTrue. I always tell people to use the types from stdint.h to avoid any nasty surprises.

author: bsfeechannel
author_rank: Super Contributor
timestamp: November 03, 2018, 09:46:27 pm
content: Quote from: nForce on November 03, 2018, 01:30:33 pmBecause this is one of the "quirks", recognized by its author, Dennis Ritchie, of the C language. The asterisk * means different things in different contexts in C. The most confusing aspect of the use of the asterisk is exactly in declarations.On the left side of the equals sign of a declaration an asterisk means " is a pointer to a(n)". C is read from the right to the left. Sochar *c;means "cis a pointer to achar".On the right side of the equals sign, the asterisk means "get the value pointed by". Sochar *c = (char) *p;means "get the value pointed by p(which is an integer), cast it as a char and attribute it to c, which is a pointer to a char.You'll be yelled at by the compiler, because c expects an address, and you are attributing it a char.When you writechar *c = (char *) p;you are telling the compiler to take the value of p (not the value pointed by p), cast it to a pointer to a char, which is an address and attribute it to c.However, if I'm not mistaken, the C standard frowns upon the fact that (char *) p is not a constant expression. Your compiler may say "bugger that", anyway.

author: nForce
author_rank: Frequent Contributor
timestamp: November 04, 2018, 02:00:19 pm
content: ThanksI have another question: Why can't we use this syntax for multi-dimensional arrays:Code:void pointerArray(int **A){//Some code}int main() {int somearray = {{1,2,3},{4,5,6}};pointerArray(somearray);return 0;}Because we can use for the one-dimensional array as this :Code:void pointerOneDimensional(int *A){//Some code}Thanks again, you guys are great.

author: sokoloff
author_rank: Super Contributor
timestamp: November 04, 2018, 02:17:08 pm
content: Because int** doesn't convey the dimension of the array.Give the compiler an int **A, and ask the compiler to dereference A. Without knowing the dimensions (which int** doesn't carry), it can't do it.An int array is not stored as an array of 2 int*s, each pointing to an array of 3 ints, but rather more likely stored as a block of 6 ints, with the compiler knowing how to calculate the offsets into the array to make it appear as a 2-by-3 array.I don't know if the specrequiresthis behavior, but it seems almost universal.

author: newbrain
author_rank: Super Contributor
timestamp: November 05, 2018, 10:15:40 am
content: Quote from: sokoloff on November 04, 2018, 02:17:08 pmYes, it does!Chapter 6.5.2.1 ofC99(AFAICR, unchanged from C89), end of clause 3:QuoteThe example following in clause 4 is very close to your description.Quote from: joshtyler on November 03, 2018, 07:39:52 pmNo typecast there, I see only an implicit conversion.Quote from: joshtyler on November 03, 2018, 07:39:52 pmYou might be mixing up C and C++.In C one can go from pointer to void to pointer to object and back without casts (C89 chapter 6.3.2.3).In C++ only going from pointer to object to pointer to void is allowed without an explicit cast.But maybe you are thinking of an implicit conversion also in this case.

author: sokoloff
author_rank: Super Contributor
timestamp: November 05, 2018, 11:19:19 am
content: Quote from: newbrain on November 05, 2018, 10:15:40 amI normally look those things up myself, but didn't have time at that moment.Very much appreciate you finding and giving the reference. Thanks!

author: joshtyler
author_rank: Contributor
timestamp: November 05, 2018, 07:20:13 pm
content: Quote from: newbrain on November 05, 2018, 10:15:40 amI was using typecasting to cover both explicit (using a cast operator) and implicit type conversions, as I've usually heard it used to mean both. However I can see that there can be some ambiguity there since, as far as I am aware, the C standard doesn't actually use the term "typecasting" anywhere!Edit: Having a quick google, it does seem that whilst the terms are used loosely, typecasting is usually reserved for explicit casting, rather than implicit conversion. So I can see that my comment was likely misleading.

author: nForce
author_rank: Frequent Contributor
timestamp: November 05, 2018, 07:27:17 pm
content: Quote from: sokoloff on November 04, 2018, 02:17:08 pmHow would be the correct way to pass a multidimensional array to a function?And why is this equal:Code:char **argv//andchar *argv[]?

author: helius
author_rank: Super Contributor
timestamp: November 05, 2018, 08:01:35 pm
content: Quote from: nForce on November 05, 2018, 07:27:17 pmThey aren't equal! When you declare a variable, a pointer type (with a *) is only a scalar variable that holds an address. An array type (with []s) is an array variable with multiple slots. However,when declaring the parameters in a function prototype, they both mean that a pointer is passed. This is because array variables are converted to ("decay to") pointers when they are used in any way other than with the [] orsizeofoperators.Together with the definition of strings as arrays, this has some surprising consequences. Consider themain(int argc, char *argv[])function. You can write the second parameter aschar **argv, and aschar argv[][], as well: they mean the same thing in a function prototype.Themainfunction never knows the dimensions ofargv. Instead, it relies on the fact that strings are null-terminated and the number of strings in theargvarray is passed in the first argument,argc. So when it callsstrlen(argv), it is guaranteed to only access legal elements of the multidimensional array, as long as(j<argc)is true. The result ofstrlentells it how many elements of the given string it can access.This dependence on the data content being well-formed to prevent out-of-bounds memory access is one reason C is weakly typed.Quote from: nForce on November 05, 2018, 07:27:17 pmYou can define a parameter with dimensions except for the most-major:Code:int example(int multi[]) {if (multi == 0) ...}The important bit is that the minor (rapidly varying) dimensions are known so that the function knows how to calculate the address.multiis the same as *(l + (z * k + (y * j + &multi))) where x, y, and z were the dimensions given in the array's definition.Here's an example:https://onlinegdb.com/HyYy9HCn7

author: newbrain
author_rank: Super Contributor
timestamp: November 06, 2018, 09:05:02 am
content: As helius correctly said, they are not equal.This is a source of confusion for many C beginners (and not so much beginners!): the false equivalence between pointers and arrays.The most important rule is "array of typedecays topointer to typewhen used in an expression context", supplemented by the one for function parameters: "the declaration of aarray of typefunction parameter is adjusted topointer to type".This is easily demonstrated with, e.g., the sizeof operator which can take either a parenthesized type or an expression, yielding clearly different results:Code:newbrain@neutron:~$ cat sizeof.c#include <stdio.h>int a;void func( int parm ){printf("\nIn func()\n sizeof(parm)\t\t%zu\n sizeof(parm+0)\t\t%zu\n", sizeof(parm), sizeof(parm+0));}int main(void){printf("In main():\n sizeof(a)\t\t%zu\n sizeof(a+0)\t\t%zu\n", sizeof(a), sizeof(a+0));func( a );return 0;}newbrain@neutron:~$ ./sizeofIn main():sizeof(a) 40sizeof(a+0) 8In func()sizeof(parm) 8sizeof(parm+0) 8If the above rules are taken into consideration, the ambiguity is minimal. I won't go into C99 VLAs and [static nnn] function parameters

author: nForce
author_rank: Frequent Contributor
timestamp: November 06, 2018, 01:14:12 pm
content: Sorry but I still don't understand:QuoteSo if I add another parameter to a functionint sizewhere size means the size of array, I can have**array(pointer to a pointer) instead of writing as anarray[][]?

author: sokoloff
author_rank: Super Contributor
timestamp: November 06, 2018, 01:33:25 pm
content: Well, sort of, but almost surely not the way you're hoping.If you take your original code and modify it along the lines above, the following works, but you still have to keep track of array indices yourself and do the offset math manually rather than having the compiler do it for you:Code:#include <stdio.h>void pointerArray(int *A, int size) {for (int i = 0; i < 2; i++)for (int j = 0; j < size; j++)printf("A[%d][%d] = %d\n", i, j, A[size * i + j]);}int main() {int somearray = {{1,2,3},{4,5,6}};pointerArray(&somearray, 3);return 0;}

author: newbrain
author_rank: Super Contributor
timestamp: November 06, 2018, 02:56:38 pm
content: Quote from: sokoloff on November 06, 2018, 01:33:25 pmYou left antoo much, the correct line would be:Code:printf("A[%d][%d] = %d\n", i, j, A[size * i + j]);Explanation:Theypostfix operator takes its two arguments (one must be a pointer, the other one a scalar), sums them and then dereference the resulting pointer (C99 standard: 6.5.2.1 Array subscripting).It is completely equivalent to(*((x)+(y))).Awill then yield an integer, so the original line will throw an error during compilation, in fact:Code:newbrain@muon:~$ make intarr CFLAGS+="-std=c99 -pedantic"cc -std=c99 -pedantic intarr.c -o intarrintarr.c: In function ‘pointerArray’:intarr.c:6:44: error: subscripted value is neither array nor pointer nor vectorprintf("A[%d][%d] = %d\n", i, j, A[size * i + j]);^<builtin>: recipe for target 'intarr' failedmake: ***  Error 1Note that the [] operator is commutative, so(size * i + j)gives exactly the same result.

author: sokoloff
author_rank: Super Contributor
timestamp: November 06, 2018, 03:16:46 pm
content: Quote from: newbrain on November 06, 2018, 02:56:38 pmDamn it. You're right. Thanks for the fix.I probably ought to compile the code I post as answers to questions.

author: nForce
author_rank: Frequent Contributor
timestamp: November 06, 2018, 07:33:43 pm
content: Hmm, why is thenargv[][]? Argv as a 2-dimensional array, if we have just parameters in a 1D array, like this: ['first', 'second']?So we should have just 1D array like c# or java has.Sorry but these concepts aren't easy.

author: NorthGuy
author_rank: Super Contributor
timestamp: November 06, 2018, 07:59:49 pm
content: Quote from: nForce on November 06, 2018, 07:33:43 pmIt is 1-dimensional - declared as "char* argv[]" - array which consists of pointers to char, same as "char** argv""argv" will give you the pointer. This pointer points to a string, which is an array of chars. "argv" will give you a char from the char array pointed to by "argv".

author: nForce
author_rank: Frequent Contributor
timestamp: November 07, 2018, 09:14:03 am
content: So it's stored like this:{{'f','i',r','s','t'},{'s','e','c','o','n','d'}}?

author: newbrain
author_rank: Super Contributor
timestamp: November 07, 2018, 09:43:55 am
content: Quote from: nForce on November 07, 2018, 09:14:03 amNo, actually you do not know where they are stored.There's no guarantee that the the strings will be contiguous in memory, you are only guaranteed that they can be modified and will keep the values you assign.See the attached picture.

author: sokoloff
author_rank: Super Contributor
timestamp: November 07, 2018, 11:26:19 am
content: Quote from: newbrain on November 07, 2018, 09:43:55 amEach string will, of course, be contiguous in memory. You just have no guarantee that argv will be immediately before argv. (I know that newbrain knows this; this clarification isn't for them.)Also note that C strings are zero-terminated, so in memory they are actually:{{'f','i',r','s','t',0} <arbitrary amount of gap, perhaps even negative> {'s','e','c','o','n','d',0}}

author: legacy
author_rank: Super Contributor
timestamp: November 07, 2018, 12:29:23 pm
content: Code:#define my_arg_size 27typedef struct { char_t line; } my1_arg_t;typedef char_t my2_arg_t;#define my_arg_n 10private void do_init(my1_arg_t my1_arg[],my2_arg_t my2_arg[]){uint32_t i;uint32_t j;for (j = 0; j < my_arg_n; j++){for (i = 0; i < my_arg_size - 1; i++){my1_arg.line = 'a' + i;my2_arg = 'a' + i;}my1_arg.line = '\0';my2_arg = '\0';}}private void do_show(my1_arg_t my1_arg[],my2_arg_t my2_arg[]){uint32_t i;uint32_t j;for (j = 0; j < my_arg_n; j++){console_out_uint32("", j, ": ");console_out_string("[", my1_arg.line, "] ");console_out_string("[", my2_arg, "] ");console_out_cr();}}Code:private void app(){my1_arg_t my1_arg;my2_arg_t my2_arg;do_init(my1_arg, my2_arg);do_show(my1_arg, my2_arg);console_out_uint32("sizeof(my1_arg_t))=", sizeof(my1_arg_t), "\n");console_out_uint32("sizeof(my2_arg_t))=", sizeof(my2_arg_t), "\n");}Code:OrangeCube raaa $ obj/app0:  1:  2:  3:  4:  5:  6:  7:  8:  9:  sizeof(my1_arg_t))=27sizeof(my2_arg_t))=27

author: sokoloff
author_rank: Super Contributor
timestamp: November 07, 2018, 01:04:27 pm
content: More fun with typecasting using legacy's code:Code:#define my_arg_size 27#include <stdint.h>#include <stdio.h>typedef char char_t;typedef struct { char_t line; } my1_arg_t;typedef char_t my2_arg_t;#define my_arg_n 10void do_init(my1_arg_t *my1_arg,my2_arg_t *my2_arg){uint32_t i;uint32_t j;for (j = 0; j < my_arg_n; j++){for (i = 0; i < my_arg_size; i++){my1_arg.line = 'a' + i;my2_arg = 'a' + i;}my1_arg.line[my_arg_size-1] = '\0';my2_arg[my_arg_size-1] = '\0';}}void do_show(my1_arg_t my1_arg[],my2_arg_t my2_arg[]){uint32_t i;uint32_t j;for (j = 0; j < my_arg_n; j++){printf("%d: [%s] [%s]\n", j, my1_arg.line, (char *)my2_arg);}}int main(){my1_arg_t my1_arg;my2_arg_t my2_arg;do_init(my1_arg, my2_arg);do_show(my1_arg, my2_arg);printf("sizeof(my1_arg_t))=%lu\n", sizeof(my1_arg_t));printf("sizeof(my2_arg_t))=%lu\n", sizeof(my2_arg_t));// On most compilers, this will also work, though is// not guaranteed by the language standard// Assuming the packing is the same, a// my1_arg_t and a my2_arg_t are laid out the same in memoryprintf("Don't rely on this next bit, but it will work on most compilers...\n");do_show((my1_arg_t *)my2_arg, (my2_arg_t *)my1_arg);return 0;}I'd wager that last part (casting a pointer to my2_arg_t as a pointer to my1_arg_t and vice versa and calling do_show with them) going work on most modern platform C compilers, but is not guaranteed by the standard.

author: sokoloff
author_rank: Super Contributor
timestamp: November 07, 2018, 01:06:35 pm
content: Code:MacBook-Pro-2:eevblog jsokoloff$ gcc test-2018-11-07.ccMacBook-Pro-2:eevblog jsokoloff$ ./a.out0:  1:  2:  3:  4:  5:  6:  7:  8:  9:  sizeof(my1_arg_t))=27sizeof(my2_arg_t))=27Don't rely on this next bit, but it will work on most compilers...0:  1:  2:  3:  4:  5:  6:  7:  8:  9:

author: nForce
author_rank: Frequent Contributor
timestamp: November 07, 2018, 01:51:03 pm
content: Quote from: newbrain on November 07, 2018, 09:43:55 amI have made my own function which takes char **arg for testing. By the diagram which you provided:Code:void arguments(char **arg){}int main(int argc, char **argv){char a[] = {{'F', 'i', 'r','0'}, {'S', 'e', 'c','0'}};arguments(a);return 0;}But I get a compilation error. What would be then a right input?

author: sokoloff
author_rank: Super Contributor
timestamp: November 07, 2018, 02:12:58 pm
content: Code:#include <stdio.h>void arguments(char **arg){printf("%s\n", arg);printf("%s\n", arg);}int main(int argc, char **argv){char *a0 = (char *) "Fir"; // The compiler will null-terminate a string for youchar *a1 = (char *) "Sec"; // if you use string syntax// Note also that '0' is an ASCII 0 (an unshifted close-paren on most US keyboards)// while numeric 0 is the string terminator.char *a = {a0, a1};arguments(a);return 0;}Code:MacBook-Pro-2:eevblog jsokoloff$ gcc test-2018-11-07-2.ccMacBook-Pro-2:eevblog jsokoloff$ ./a.outFirSec

author: helius
author_rank: Super Contributor
timestamp: November 07, 2018, 04:33:34 pm
content: Quote from: nForce on November 07, 2018, 01:51:03 pmCode:#include <stdio.h>char *arguments(char *arg, int c){return &arg;}int main(int argc, char **argv){char a[] = {{'F', 'i', 'r', '\0'},{'S', 'e', 'c', '\0'}};printf("\n%s",arguments(a,0));printf("\n%s",arguments(a,4));return 0;}Seehttps://onlinegdb.com/BkJ1NcxaXBasically, the problem is that multi-dimensional arrays are stored as a single array in row-major order, whereas arguments passed as "char **" expect to receive a pointer to another pointer. Since the variable "a" points to an array of characters (representing a multi-dimensional array) and not to a pointer, the callee function misinterprets characters as pointers and segfaults. Instead either pass the array as a pointer to char (so that the callee just sees it as a single-dimensional array) (in the above code the cast is implicit, it could be written explicitly asprintf("\n%s",arguments((char *)a, 0))), or write the callee function to receive an array with the same minor dimensions (all dimensions except the first) so that it knows how to access it (this was myprevious code snippet).

author: newbrain
author_rank: Super Contributor
timestamp: November 07, 2018, 05:58:36 pm
content: Quote from: nForce on November 07, 2018, 01:51:03 pmDoes the compilation error point to a specific problem?Let me guess, is it's something like "incompatible pointers"?In reality, you are not following my diagram (so, it was not clear enough, my bad).Let's see what's happening here:Thearguments()function expects a pointer to pointer to charYou have declared a bi-dimensional array, with two rows of 4 character each.The array, according to the rules, decays to a pointer to its first element when used in an expression (in this case, the function parameter).The first element of the bi-dimensional array is, in fact, a 4 element array of char.The function is then receiving a pointer to a 4 element array of char.A pointer to a 4 element array of char is not compatible with a pointer to pointer to char.Sadness ensues. No segfault, though, the program will not compile.Quite a mouthful, I hope I made it clear enough...So, what do we need to have this code working?Something's gotta give: either we change the array, or we change the function parameter.Let's go with the first option:Code:void arguments(char **arg){}int main(int argc, char **argv){char *a[] = { "Fir", "Sec" };arguments(a);return 0;}And now, let's go through the same exercise of analyzing the code!As before, the arguments() function expects a pointer to pointer to charWe have now have declared a one-dimensional array of pointer to char with two elements*.The array, according to the rules, decays to a pointer to its first element when used in an expression (in this case, the function parameter).The first element of the array is, in fact, a pointer to char.The function is then receiving a pointer to a pointer to char.So, the type of the formal parameter matches 1-to-1 the type of the actual parameter.Profit!*Note, an important one at that:A string literal (something between double quotes, 6.4.5) is an array of char, so it makes sense that, when used as an initializer, it decays to a pointer to char. The standard makes also explicit provision for a string literal to be used as initializer for an array of characters, 6.7.8 clause 14. So you could have written your original array as:Code:char a[] = {"Fir", "Sec" };As helius points out, the terminating NUL character is added automatically for string literals.And this brings us to the second way to correct the code, changing arguments() parameter type:Code:void arguments(char (*arg)){}int main(int argc, char **argv){// char a[] = {{'F', 'i', 'r','0'}, {'S', 'e', 'c','0'}};char a[] = { "Fir", "Sec" }; // As good as yours, but convenient...arguments(a);return 0;}I won't go through the whole story again, just take note that the function now takes (using the old trick of reading complicated declarations from the inside):A parameter called arg:argWhich is a pointer:*argTo an array of 4 elements:(*arg)Of type char:char (*arg)HTH!

author: nForce
author_rank: Frequent Contributor
timestamp: November 08, 2018, 05:45:37 pm
content: Yes, it helps.Let's go in depth, with this statement:QuoteYou mean by this, that the pointer points to an array of char? And these elements can have an arbitrary space between them as sokoloff pointed out.But crucial here is if it points to an array of char in memory. So where an array starts in memory, there should point the pointer.

author: newbrain
author_rank: Super Contributor
timestamp: November 08, 2018, 10:29:51 pm
content: Quote from: nForce on November 08, 2018, 05:45:37 pmLet's take the declaration againCode:char *a[] = { "Fir", "Sec" };We are declaring an object named a:aWhich is an array:a[]Of pointers:*a[]To char:char *a[]Its size is two elements, as, though not directly specified, it is set by the initializer.Now, this is almost exactly as the "argv" data structure is defined (nitpicking details below^).When the name a is used in any expression (i.e. more or less any place that is not the declaration itself or the single parenthesized argument of a sizeof operator) the general rule applies: it will decay to a pointer to its first element, giving, in fact, a char **.(And this is where the second rule would apply: a function parameter, if declared as char *arg[], is adjusted to yield a pointer, so it's equivalent to char **arg.)This pointer ( "a" ) does not point to an array of char, but to an array of pointers to char (the elements of a).Each element, in turn, is pointing to an array of char in memory, allocated where the compiler sees fit^.This realizes exactly what my original picture tried to show: an array of char*, and the relative pointed to array of chars.You might say, simplifying, that in C arrays are how stuff is stored in memory, but actual access to them is always realized through pointers, possibly with the help of the [] operator.Pointers "know" the size of the type they are pointing to, so pointer arithmetic and [] works as expected.Pointers do not know how many elements they are pointing to (0 is a corner case, 1 or more cover the case of a single variables and arrays).A null pointer to any type has the value 0 and does not point to anything.^Nitpicks: argv (or whatever you might want to call it, the name is just conventional) will always contain also a final null pointer element. Furthermore, the char arrays that represent string literals ("Fir", "Sec") are not modifiable (6.4.5, clause 6), while the ones pointed by argv elements can be modified (5.1.2.2.1, end of clause 2).

author: newbrain
author_rank: Super Contributor
timestamp: November 08, 2018, 11:06:50 pm
content: Quote from: sokoloff on November 07, 2018, 01:04:27 pmIt is actually guaranteed by the standard.Two sections are relevant:6.7.2.1 Structure and union specifiers, clause 13:"...A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning."6.3.2.3 Pointers, clause 7:"...When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object."So, not only the layout is the same but since to be passed to printf they are converted to char* (permitted by 6.3.2.3), it's not even undefined behaviour to print them!(At least, I read it that way, but I'm ready to be corrected).

author: sokoloff
author_rank: Super Contributor
timestamp: November 08, 2018, 11:38:25 pm
content: I think that the key phrase is that "there may be unnamed padding within a structure object", meaning that a my1_arg_t might be (consistently) padded out to 2, 4, or 8 bytes in size and have the implementation still be a conforming C compiler. It more commonly comes up with bitfields than with a struct of a simple char.

author: newbrain
author_rank: Super Contributor
timestamp: November 09, 2018, 07:14:58 am
content: Cor blimey, what was I thinking?I should not post late at night.I simply overlooked the fact that we are using an array of structures vs. an array of arrays, and only considered the first members.You are perfectly right.Apart from the first element that must have the same layout in both arrays, the pointers might not be correctly aligned.The mere possibility means that we are using undefined behaviour.Quote from: sokoloff on November 08, 2018, 11:38:25 pmEtA: My post holdsonly when going to and from pointers to a specific elementand then accessing it as a char[]

author: sokoloff
author_rank: Super Contributor
timestamp: November 16, 2018, 01:41:47 pm
content: An interesting article on the peculiarities of individual machines.https://begriffs.com/posts/2018-11-15-c-portability.html

author: newbrain
author_rank: Super Contributor
timestamp: November 17, 2018, 12:14:51 pm
content: Quote from: sokoloff on November 16, 2018, 01:41:47 pmThanks! A nice memory trip...I don't see why they seem to think big endianness is an oddity, though it's becoming less and less common.It's probably quite difficult to provide a (reasonably efficient) standard conforming implementation for some of those architectures.For the most part, though, the various quirks can be easily ignored by abiding to a simple rule:"Thou shalt not use undefined behaviour".And if one wants to write portable programs, also implementation defined behaviour should be kept as low as possible.One's complement and sign magnitude integer representations are explicitly supported by the standard (AFAICR, those and two's complement are the only admissible ones).Non-zero null pointers look odd, but the compiler just needs to know what to do with a '0' constant expression when it converts it to a pointer.

author: andersm
author_rank: Super Contributor
timestamp: November 17, 2018, 01:42:38 pm
content: Quote from: newbrain on November 17, 2018, 12:14:51 pmC++20 willonly allow two's complement, and it is expected that the next revision of the C standard will follow suit. Right-shift of signed integers will also finally be codified as sign-extending.