Page 1 of 1

A word of warning about extern: pointers and arrays

Posted: Wed Jul 03, 2013 1:47 pm
by dciliske
Simply put I was working with someone elses code (don't really know who's) and found a super nasty bug where the processor would take off into a random memory space and die. The problem is, everything looked good and correct in the source, so why was it blowing up? To understand, answer for yourself the question, what's the difference in meaning (both allocation and use) of the following:

Code: Select all

type * foo = malloc(n*sizeof(type));
type   foo[n];
If we ignore the possibility of a malloc failure, then there is effectively no difference and anything you can do with one, you can do in exactly the same way with the other.

Now, let's consider the following (in a different source file, of course):

Code: Select all

extern type * foo;
extern type   foo[];
Is anything different? Can they still be used the same way? If you said yes, sorry, you're going to be reading garbage, and if it's a list of functions, you're now in random memory land...

So, if they're different, how are they different? why are they different?

To understand what's going on, you have to understand what extern is telling the compiler. extern says to the compiler that it won't find the token declared or allocated anywhere in the current source file, but that where it is defined, it's the given type. This is why when you use externs but forget to declare the variable somewhere else the linker fails with 'undefined reference'; this is an interesting point, because it doesn't say 'unallocated variable'. The reason for 'undefined reference', is that declaring something extern causes the linker to look for a 'reference' or 'symbol' to a memory location containing the data.

In the event where we declare an extern pointer, we're telling the linker that at the location of the symbol exists data that is a pointer to something else. However, when we declare an extern array instead, the linker knows that the symbol is actually an array starting where it found the symbol. Therefore, when we use the [] operator to get at an individual element, the data at the pointer symbol's location will be dereferenced to find the array, but the data at the array symbol's location is treated as the array itself.

Hope this serves as an education to folks, because of the dozen or so C/C++ devs I asked about this this week, none of them knew the difference. Did any of you know?

-Dan

[Edit: added length to local array declaration per ecasey's comment below.]

Re: A word of warning about extern: pointers and arrays

Posted: Wed Jul 03, 2013 8:20 pm
by roland.ames
I had something similar to this only the other day while trying to access AppName.

I wanted to have (in a different source file to where AppName is defined)

Code: Select all

extern const char * AppName;
extern const char miscstring1[];
extern const char miscstring2[];

const char * strptr[3] = 
{
    AppName,
    &miscstring1,
    &miscstring2,
};

void fn(int i)
{
    iprintf("%s\n", strptr[i]);
}
but this didn't work, because the compiler can't initialise appnameptr.

I tried changing the definition of ApppName to

Code: Select all

const char AppName[] = "XXXX";
so I could access it from another file using,

Code: Select all

extern const char AppName[];
extern const char miscstring1[];
extern const char miscstring2[];

const char * strptr[3] = 
{
    &AppName,
    &miscstring1,
    &miscstring2,
};

void fn(int i)
{
    iprintf("%s\n", strptr[i]);
}
which worked, but doing this stops the IPSetup / AutoUpdate tools from working, because the library source was built with

Code: Select all

extern const char * AppName;
so whenever I used IPSetup, the app would hang

I ended up doing this

Code: Select all

extern const char * AppName;
extern const char miscstring1[];
extern const char miscstring2[];

const char * miscstring1ptr = &miscstring1;
const char * miscstring2ptr = &miscstring2;

const char ** strptrptr[3] = 
{
    &AppName,
    &miscstring1ptr,
    &miscstring2ptr,
};

void fn(int i)
{
    iprintf("%s\n", *strptrptr[i]);
}
which does what I want

Re: A word of warning about extern: pointers and arrays

Posted: Thu Jul 04, 2013 12:02 pm
by ecasey
Dan,

In your first example you allocate memory for *foo to point to, but you leave foo[] as a null pointer. Would

Code: Select all

type * foo = malloc(n*sizeof(type));
type   foo[n];
not work, even as an extern?

It seems to work for me, although it the malloc has to have run before using the extern foo, otherwise it too is a null pointer.

Ed

Re: A word of warning about extern: pointers and arrays

Posted: Fri Jul 05, 2013 3:00 pm
by tod
Roland,
I think all you needed to do was get rid of the leading ampersand (and possibly the trailing comma). I changed your code to

Code: Select all

const char * strptr[3] = 
{
    AppName,
    miscstring1,
    miscstring2
};
and it compiled and ran with no problem. Taking the address of an array name should result in a pointer to an array (which decays to a ptr to a ptr) and should have produced a compiler conversion error.

Tod

Re: A word of warning about extern: pointers and arrays

Posted: Sun Jul 07, 2013 9:44 pm
by roland.ames
this works OK if strptr is in the main.cpp file where AppName is defined.

But I needed it to be in a separate source code file ( which is a src.c file rather than src.cpp)

if I try this in source.c

Code: Select all

extern const char * AppName;
extern char * miscstring1;
extern char * miscstring2;

const char * strptr[3] = 
{
    AppName,
    miscstring1,
    miscstring2
};
I got two errors on the line within the initialisation of strptr containing AppName,
(near initialization for 'strptr[0]')
initializer element is not constant

Re: A word of warning about extern: pointers and arrays

Posted: Mon Jul 08, 2013 1:11 pm
by tod
I would have to guess that this is the C compiler objecting to a const variable instead of a true constant expression. I too put strptr in a separate file named test.cpp. Changing it to test.c produced the error you see. If it's just a C vs C++ linkage issue that's making you stick with .c, you could just rename the file .cpp and wrap all the functions in the .h with

Code: Select all

extern "C"
{}
That way you'll get the C linkage but the more tolerant C++ compiler.

Re: A word of warning about extern: pointers and arrays

Posted: Mon Jul 08, 2013 3:42 pm
by tod
Dan,
I found your post interesting but I'm having a little trouble understanding what caused you grief. First, I don't use arrays very much so I'm far from being an expert on this but I'm not going to let that stop me! :) First the statement

Code: Select all

If we ignore the possibility of a malloc failure, then there is effectively no difference and anything you can do with one, you can do in exactly the same way with the other
isn't quite true. Let me use the names fooPtr and fooArray just to make things a little clearer.
  1. fooArray[n] can be passed as formal reference parameter. For example in the case of n = 3, we could have a function with this signature

    Code: Select all

     void test2(int (&someIntArray)[3]);
    Now the only valid thing that can be passed to this method is an integer array of length 3. You can pass fooArray (assuming "int fooArray [3]") but you can't pass fooPtr.
  2. Roland's contribution brought into focus for me that fooArray is seen by the compiler as a constant expression. fooPtr can never be more than a const variable. That is, fooArray is always associated with the memory location for the array and can't be made to point at anything else. Even "const int* const fooPtr" can't make that claim (because of const_cast). While I think it is common practice to think of fooArray as decaying to a pointer, it's probably better to think of it decaying to a memory address. Using the [] operator on an address (whether it comes from a pointer or an array name) should serve to dereference that address and perform the correct pointer arithmetic specified by the value inside the brackets and the type. Passing fooArray to another method that takes a pointer should truly decay the parameter to a pointer.
  3. Taking the address of both things is very different but that doesn't seem germane to your topic and doing something like &fooArray seems problematic to me.
So what caused the problem? I don't see how extern has any effect, the compiler and the linker should both know that one is an array and the other is a pointer. If the variables are shared via extern I'm missing the point on how this is changing anything assuming you aren't using formal reference parameters or running into constant vs const var issues.

Re: A word of warning about extern: pointers and arrays

Posted: Mon Jul 08, 2013 8:28 pm
by roland.ames
tod wrote:I would have to guess that this is the C compiler objecting to a const variable instead of a true constant expression. I too put strptr in a separate file named test.cpp. Changing it to test.c produced the error you see. If it's just a C vs C++ linkage issue that's making you stick with .c, you could just rename the file .cpp and wrap all the functions in the .h with

Code: Select all

extern "C"
{}
That way you'll get the C linkage but the more tolerant C++ compiler.
Unfortunately there are other reasons (third party supplied C code) for sticking with C for this file, but I think I will split it into two files, a .c file for the stuff that has to remain C, and a cpp file for everything else, so I can take advantage of the features of c++.

Thanks for all the info on this.

Roland.