One of job interview questions on C pointers here is the following: what is null pointer assignment error?
I've googled for a while and don't see any reasonable explanation. What is that? Trying to write through a null pointer? Something architecture- or environment-specific? What exactly is that error?
http://www.faqs.org/qa/qa-3786.html
A NULL pointer assignment is a runtime error
It occurs due to various reasons one is that your program has tried to access an illegal memory location.
Illegal location means either the location is in the operating systems address space or in the other processes memory space.
In stdio.h NULL is defined as 0
So whenever your program tries to access 0th location the operating system kills your program with runtime assignment error because the 0th location is in the operating systems address space and operating system doesn't allow access to its address space by user program .
Example code:
int* ptr = NULL;
*ptr = 3;
Explanation:
On almost every system, address 0 is reserved. System won't allow you to write to that location. If you try, you will get a runtime exception (access violation, segmentation fault, etc.).
I actually can not recall the source, but according to the source, this run time error is restricted to small and medium memory models being put into use by corresponding compiler. You see, as told before, the null pointer actually does not points to zero, in fact different compilers use different but fixed memory location to be used as null pointer.
Lets consider the case of TC compiler, this compiler places four zero bytes at the bottom of the data segment and TC copyright notice. TC also uses DS:0000 location, bottom of the data segment as null pointers location. So, assigning a value to this null pointer, would actully change the four bytes and probably, mess up the copyright notice.
Now, at the program termination, the four zeros and copyright banner are checked for any kind of alteration. If any alterations are found, it generates a Null Pointer Assignment error.
So, I think its not just the null pointer, any pointer that gets wild, if tries to access some key areas, you are greeted with Null Pointer Assignment Error.
There are many scenarios where you can see problems. But the key thing is, you did not allocate the memory correctly. The following code would produce Null pointer assignment error message after you run the program. Note: It will compile correctly.
void CopyMessage(char *p)
{
strcpy(p, "welcome");
}
void main()
{
char *src;
CopyMessage(src);
}
It is a run time error occurs when you try to point illegal memory space, usually address 0 which is reserved for OS.
My intent in this answer is supplemental to the most basic of concepts of a null pointer. This simplest definition, as listed in many places is whenever a base value pointing to an address gets assigned a '0' or NULL value because the zero page, 0th memory location is part of the operating systems address space and operating system doesn't allow access to its address space by the user's program. In such cases the pre-compiler or compiler may generate an error, or the error can be generated by the operating system itself during run time as a memory access violation.
The following discussion of null pointers is based on concepts contained in the programming that occurs at the machine level of a language that allows fine control and requires understanding of how variable space is addressed.
Most high level languages and compilers may prevent this from occurring with appropriate 'type' casting, and specifying an option base and making no mental miscalculations in indexing. 'C' as a language, without specifying the strictest of compiler parameters, is particularly prone to these types of errors, as well as less sophisticated machine based compiler or programming languages as found today in "pocket' processors.
However, since the inception of computers and programming languages the concept of a zero pointer error expanded to include any pointer that points to the 0th location in any protected memory location. But especially within the context of how a memory location that can be used to point to any memory location can be unintentionally overwritten to contain a null value. And here I examine this concept because of what I've called 'the error of 1' which occurs when programmers have to switch between option base '0' or option base '1'. This is a counting issue where we begin our count with '0' or '1' as in:
Option Base 0
[0,1,2,..,9] or
Option Base 1
[1,2,3,...,10]
for an array with 10 elements. An error of 1 can create a miscalculation which results in a pointer to the first memory location 'before' the array,
Option Base 1
0[1,2,3,...,10]
^
|Last memory location of another variable space 'before' this variable space
or the first position 'after' the array which by definition is out of bounds.
Option Base 0
[0,1,2,...,9]10
^
|First memory location of another variable after this variable
But, when dealing with programming that uses direct memory access, as in the original machine code of any language, an error of 1 can be tragic placing an unintended value a memory location outside of the intended range which in the case of variable space and using pointers is the variable space before or after the intended variable, which when the array is initialized or cleared creates a 'null' or 0 in an undesired location, and especially if it's an array of pointers a null pointer error in an unintended variable.
This is of course dependent upon how the variable space is structured and/or what type. Can be particularly troublesome if the variable or other storage address space is nested in the code. As I state earlier, many high level language compilers can circumvent most of this type of error; but, when writing specific subroutines in machine code, for whatever reason deemed necessary, one must take extra care to ensure that option base is explicitly defined and adhered to by practice if not compiler convention.
First, programmers recognize the necessity that both the program and the storage areas are clearly defined and that nothing, without express consent, should modify even a single bit of data. This is critical, with respect to a null pointer because the 0th memory location, in zero page area of the operating system is often used to store a stack which are memory locations pushed onto the stack for a return operation. Whether a system call pushes an address for a return operation (popping the return address from where the system was interrupted) due to a mask-able or non-mask-able interrupts, or because the programmer wants to push data, or a memory location to be later popped off of this stack. This is a protected area. Like any pointer to a valid memory address, one would not want to write to the wrong location, the 0th location is particularly susceptible if overwritten because variables are often null or have a value of 0 from an initial power up state, and thus a variable which has not been explicitly defined after power up, or has been intentionally initialized is likely to be zero.
In the case of the stack on zero page, or any stack containing return address, if values are pushed onto the stack, and not popped before a 'return' is encountered, the return value can be null or zero and the return points to the stack's memory location. This is a null pointer error, which may not generate an error but return the code pointer to an area that does not contain code, such as the middle of a stack. These exploits are well known and often used in methods to compromise a system's security to gain access by less scrupulous crackers; or can be used for ingenious access under special circumstances, or when accidental create all kinds of mischief where the source is difficult to determine.
As I stated, this description is outside the conventional definition of a null pointer error, but it can produce a null pointer nonetheless, though more often producing other errors, or none at all. It often gives no indication of its existence other than 'if or when' a program fails to perform as expected.
Here I provide additional non-conventional examples and definitions of potential sources of null pointer assignment errors, rather than defining conventional understanding which is more an error in programming convention than an error in programming logic.
This type of error (undefined, or null) is much rarer. But modern programming of 'pocket' processors, using bench top devices like an Arduino, Raspberry PI, AMD, or any other computer on a chip programming exists in a myriad of forms, many of which are as simple today as yesteryear, this null pointer problem still exists today and can occur even in the most sophisticated of systems. Additionally, companies that build their own variable or data structures are probably also the most likely people to see this type error nowadays. The intention is to show examples that can aid in recognition.
As was defined in older days, it was soon recognized that conditions which produce null pointer errors could also produce errors where the value of the pointer was unintentionally modified. Then, as a variable which is being used as a pointer and having been overwritten without the programmer's intent or knowledge, which could be a null, but could also be any other value. So, we find that an issue which can create a null pointer, can also create a non null pointer. The null pointer is a special case, which OFTEN creates an systematic error message; but, when those same conditions cause the pointer to take on a random or undetermined value instead of the original address where the data should reside, it now contains a null or unknown address which results in moving or storing data to an invalid or unwanted location potentially overwriting and corrupting that code or data.
Most will rightfully argue that this is NOT a null pointer error; and, they are completely 100% CORRECT! However, the roots of this error typically create strange often seen null pointer errors because more often the pointers will contain a 'null'! The point of this exercise in definition is to point out how the creation of null pointers can also lead to a problem which appears to have no indication of the original source of the problem. IOW, in concept there is no pointer to the problem. Because of the relationship to the creation of odd 'null' pointer issues, and in this case the subsequent lack of data that points to the source of the error because the pointer was NOT null and instead is 'undefined' old timers who have transitioned from top-down programming to object oriented event driven programming recognize this relationship and recognize this type of 'null' pointing error which appears to have no definable source.
Because this type failure, the corrupted data, or corrupted code may not immediately execute or get used at the time it is moved to an existing unused memory location. However, when the code or data does cause a problem at a later time in the run, there is no information on the 'real' location of the error because it is so far removed in time from the event that caused it. Whether it creates or assigns null pointers or creates some other corruption, it modifies code, and things can get weird, really weird.
To summarize, I define a null pointer as any null or undefined address used to point to a memory location regardless of what originally creates it. A null pointer assignment error, or many other errors, can be assigned to this issue and example.
In simpler architecture or programming environments, It can refer to any code which unintentionally ends up creating nulls as pointers, or creates a bug that in anyway halts the execution, like overwriting a byte in the return stack, overwriting code, code that accidentally stores a '0' in a bad location, existing code, or just as data in the wrong location, not just as an address.
So, while the examples above, work fine to define an example of a null pointer. So we expand the concept, A null pointer is any pointer which gets used as a variable pointer, and the address location of that variable for any one of multiple reasons now contains a 'null' or ANY unintended value that causes it to point to an undesirable memory location no matter how it got there, not just errors in programming logic or math calculation errors. IOW, not just a 0 in a pointer; more specifically a zero or undefined value in any memory location where that memory location was not the specific target and under other circumstances had an OTHER purpose for which it will now perform!
So, finally, one can get a null pointer error, and upon examining the pointer finds it contains a null; but, cannot find the code that placed the null into the pointer or assigned it. This is a broadest definition of null pointer assignment error, and is absolutely the worst case scenario of a null pointer error. When this occurs in a large program, it often results in the death of the program because if the error existed in previous versions, but was writing to unintended memory locations (which allowed the program to function or IOW) which was previously accessible but unallocated in earlier versions, the error goes unnoticed until the program gets expanded, and now that once previously unused memory location contains new code OR data which allows the old bug to now generate random errors in the new code or corrupts data!
For example: in an earlier version, the bad address value causes data to be written outside of the defined variable spaces, but goes unnoticed for several versions because the data is being written, and it being read, and the program and everything 'appears' OK! But, as the program expands, new code now exists relatively in the same relative address space as the memory where the original old bug had been incorrectly writing to the wrong memory location and no one noticed, whether one byte, or a whole block of data! But, now, there exists new code there. And when the program runs that particular code, today or tomorrow as whatever function that contains it is called, the new data gets corrupted by the old undiscovered bug.
Finding the 'original' error which existed a year earlier is now almost, if not completely, impossible to find.
Administrator and developer logic usually dictates, why should I look there, we know that code ran and worked just fine for that last several versions. But, now, part of the new code does not work, a major pieces are broken. We look and look and nothing does one find. It's as if the error doesn't exist, and yet it does. What's causing it, who suspects code written years earlier? Null pointers and many other errors are caused by this too. With understanding, and a good editor that can examine code directly, appropriate monitors to watch modified memory locations that trigger a halt to determine the code being executed at right time, even this can be found.
Related
I spent an embarrassing amount of time last night tracking down a segfault in my application. Ultimately, it turned out I'd written:
ANNE_SPRITE_FRAME *desiredFrame;
*desiredFrame = anne_sprite_copy_frame(&sprite->current);
instead of:
ANNE_SPRITE_FRAME desiredFrame;
desiredFrame = anne_sprite_copy_frame(&sprite->current);
In line 1 I created a typed pointer, and in line 2 I set the value of the dereferenced pointer to the struct returned by anne_sprite_copy_frame().
Why was this a problem? And why did the compiler accept this at all? All I can figure is that the problem in example 1 is either:
I'm reserving space for the pointer but not the contents that it points to, or
(unlikely) it's trying to store the return value in the memory of the pointer itself
In line 1 I've created a typed pointer, and in line 2 I set the value of the dereferenced pointer to the struct returned by anne_sprite_copy_frame().
Both of these are allowed in C, which is why this is perfectly acceptable by the compiler.
The compiler doesn't check to make sure your pointer actually points to anything meaningful - it just dereferences and assigns.
One of the best and worst features of C is that the compiler does very little sanity checking for you - it follows your instructions, and does exactly what you tell it to do. You told it to do two legal operations - even though the variables were not initialized properly. As such, you get runtime issues, not compile time problems.
I'm reserving space for the pointer but not the contents that it points to
Yeah, exactly. But the compiler (unless it does some static analysis) can't infer that. It only sees that the syntax is valid and the types match, so it compiles your program. Dereferencing an uninitialized pointer is undefined behavior, though, so your program will most likely work erroneously.
The pointer is uninitialized, but it still has a value so it points somewhere. Writing the return value to that memory address overwrites whatever happens to be there, invoking undefined behavior.
Technically the compiler is not in the business of telling you that a syntactically valid construct will result in undefined (or even likely unexpected) behavior, but I would be surprised if there was no warning issued about this particular usage.
C is weakly typed. You can assign anything to anything with the obvious consequences. You have to be very careful and disciplined if you do not want to spend nights uncovering bugs that turn out "stupid". I mean no offense. I went through the same issues due to an array bound overflow that overwrote other variables and only showed up in some other part of the code trying to use these variables. Nightmare! That's why Java is so much easier to deal with. With C you are an acrobat without a net, with Java, you can afford to fall. That said, I do not mean to say Java is better. C has its raison d'etre.
If I define a structure...
struct LinkNode
{
int node_val;
struct LinkNode *next_node;
};
and then create a pointer to it...
struct LinkNode *mynode = malloc(sizeof(struct LinkNode));
...and then finally free() it...
free(mynode);
...I can still access the 'next_node' member of the structure.
mynode->next_node
My question is this: which piece of the underlying mechanics keeps track of the fact that this block of memory is supposed to represent a struct LinkNode? I'm a newbie to C, and I expected that after I used free() on the pointer to my LinkNode, that I would no longer be able to access the members of that struct. I expected some sort of 'no longer available' warning.
I would love to know more about how the underlying process works.
The compiled program no longer has any knowledge about struct LinkedNode or field named next_node, or anything like that. Any names are completely gone from the compiled program. The compiled program operates in terms of numerical values, which can play roles of memory addresses, offsets, indices and so on.
In your example, when you read mynode->next_node in the source code of your program, it is compiled into machine code that simply reads the 4-byte numerical value from some reserved memory location (known as variable mynode in your source code), adds 4 to it (which is offset of the next_node field) and reads the 4-byte value at the resultant address (which is mynode->next_node). This code, as you can see, operates in terms of integer values - addresses, sizes and offsets. It does not care about any names, like LinkedNode or next_node. It does not care whether the memory is allocated and/or freed. It does not care whether any of these accesses are legal or not.
(The constant 4 I repeatedly use in the above example is specific for 32-bit platforms. On 64-bit platforms it would be replaced by 8 in most (or all) instances.)
If an attempt is made to read memory that has been freed, these accesses might crash your program. Or they might not. It is a matter of pure luck. As far as the language is concerned, the behavior is undefined.
There isn't and you can't. This is a classic case of undefined behavior.
When you have undefined behavior, anything can happen. It may even appear to work, only to randomly crash a year later.
It works by pure luck, because the freed memory has not yet been overwritten by something else. Once you free the memory, it is your responsibility to avoid using it again.
No part of the underlying Memory keeps track of it. It's just the semantics the programming language gives to the chunk of memory. You could e.g. cast it to something completely different and can still access the same memory region. However the catch here is, that this is more likely to lead to errors. Especially type-safty will be gone. In your case just because you called free doesn't mean that the underlying memory canges at all. There is just a flag in your operating system that marks this region as free again.
Think about it this way: the free-function is something like a "minimal" memory management system. If your call would require more than setting a flag it would introduce unneccessary overhead. Also when you access the member you (i.e. your operating system) could check if the flag for this memory region is set to "free" or "in use". But that's overhead again.
Of course that doesn't mean it wouldn't make sense to do those kind of things. It would avoid a lot of security holes and is done for example in .Net and Java. But those runtimes are much younger than C and we have much more ressources these days.
When your compiler translates your C code into executable machine code, a lot of information is thrown away, including type information. Where you write:
int x = 42;
the generated code just copies a certain bit pattern into a certain chunk of memory (a chunk that might typically be 4 bytes). You can't tell by examining the machine code that the chunk of memory is an object of type int.
Similarly, when you write:
if (mynode->next_node == NULL) { /* ... */ }
the generated code will fetch a pointer sized chunk of memory by dereferencing another pointer-sized chunk of memory, and compare the result to the system's representation of a null pointer (typically all-bits-zero). The generated code doesn't directly reflect the fact that next_node is a member of a struct, or anything about how the struct was allocated or whether it still exists.
The compiler can check a lot of things at compile time, but it doesn't necessarily generate code to perform checks at execution time. It's up to you as a programmer to avoid making errors in the first place.
In this specific case, after the call to free, mynode has an indeterminate value. It doesn't point to any valid object, but there's no requirement for the implementation to do anything with that knowledge. Calling free doesn't destroy the allocated memory, it merely makes it available for allocation by future calls to malloc.
There are a number of ways that an implementation could perform checks like this, and trigger a run-time error if you dereference a pointer after freeing it. But such checks are not required by the C language, and they're generally not implemented because (a) they would be quite expensive, making your program run more slowly, and (b) checks can't catch all errors anyway.
C is defined so that memory allocation and pointer manipulation will work correctly if your program does everything right. If you make certain errors that can be detected at compile time, the compiler can diagnose them. For example, assigning a pointer value to an integer object requires at least a compile-time warning. But other errors, such as dereferencing a freed pointer, cause your program to have undefined behavior. It's up to you, as a programmer, to avoid making those errors in the first place. If you fail, you're on your own.
Of course there are tools that can help. Valgrind is one; clever optimizing compilers are another. (Enabling optimization causes the compiler to perform more analysis of your code, and that can often enable it to diagnose more errors.) But ultimately C is not a language that holds your hand. It's a sharp tool -- and one that can be used to build safer tools, such as interpreted languages that do more run-time checking.
You need to assign NULL to mynode->next_node:
mynode->next_node = NULL;
after freeing the memory so it will indicate that you are not using anymore the memory allocated.
Without assigning the NULL value, it is still pointing to the previously freed memory location.
For example, in Linux, I have a pointer pointing to a task_struct. Later, the task_struct might migrate or deleted. How do I know whether the pointer still points to a task_struct or not?
It's not possible.
Pointers only contain addresses, and generally it's not possible to determine whether or not a given address is "valid".
Sometimes you can ask the entity that gave you the pointer to begin with if it's still valid, but that of course depends on the exact details of the entity. The language itself cannot do this.
You don't know, because:
a pointer just contains the address of the object it points to;
the type information is lost at compile time.
So, C provides no facilities for dealing with this kind of problems, you have to track what happens to stuff you point to on your own.
The most you can ask (and it is alreay OS-specific) is to check if the memory page where the structure would reside is still accessible, but usually it's not a particularly useful information.
Depending on your allocation pattern/luck, you might get a segmentation fault (which of course kills your program)...but that at least would tell you the reference is no longer valid.
However, as previously stated, the best way is to track the validity yourself.
If you need to keep moving a struct around in memory (rather than just blanking it and reinitializing it at its current location), you could consider using a pointer to a pointer to make the tracking easier.
"ie. Everything gets a reference to the pointer to the struct, and then when you move or delete the struct you just set that pointer to NULL or to the new memory location."
Also, in general, if you want to do checks on your program for this kind of weirdness, I would recommend looking into valgrind.
It is your responsibility in C to write your code so that you keep track of it. You can use the special value of NULL (representing not pointing to anything), setting the pointer to NULL when you remove (or haven't yet set) whatever it was pointing to & testing for NULL before using it. You might also design your code in a way that the question never comes up.
There is no way to query a random pointer value to see if it represents something, just like there is no way to query an int variable to check if the value in it is uninitialized, junk, or the correct result of a computation.
It is all a matter of software design and, when necessary, using the value of NULL to designate not set.
Does the allocated memory holds the garbage value since the start of the OS session? Does it have some significance before we name it as a garbage value in our program runtime session? If so then why?
I need some advice on study materials regarding linux kernel programming, device driver programming and also want to develop an understanding on how the computer devices actually work. I get stuck into the situations like the "garbage value" and feel like I have to study something else also for better understanding of the programming language. I am studying by myself and getting a lot of confusing situations. Any advice will be really helpful.
"Garbage value" is a slang term, meaning "I don't know what value is there, or why, and for that reason I will not use the value". It is "garbage" in the sense of "useless nonsense", and sometimes it is also "garbage" in the sense of "somebody else's leavings".
Formally, uninitialized memory in C takes "indeterminate values". This might be some special value written there by the C implementation, or it might be something "left over" by an earlier user of the same memory. So for examples:
A debug version of the C runtime might fill newly-allocated memory with an eye-catcher value, so that if you see it in the debugger when you were expecting your own stored data, you can reasonably conclude that either you forgot to initialize it or you're looking in the wrong place.
The kernel of a "proper" operating system will overwrite memory when it is first assigned to a process, to avoid one process seeing data that "belongs" to another process and that for security reasons should not leak across process boundaries. Typically it will overwrite it with some known value, like 0.
If you malloc memory, write something in it, then free it and malloc some more memory, you might get the same memory again with its previous contents largely intact. But formally your newly-allocated buffer is still "uninitialized" even though it happens to have the same contents as when you freed it, because formally it's a brand new array of characters that just so happens to have the same address as the old one.
One reason not to use an "indeterminate value" in C is that the standard permits it to be a "trap representation". Some machines notice when you load certain impossible values of certain types into a register, and you'd get a hardware fault. So if the memory was previously used for, say, an int, but then that value is read as a float, who is to say whether the left-over bit pattern represents a so-called "signalling NaN", that would halt the program? The same could happen if you read a value as a pointer and it's mis-aligned for the type. Even integer types are permitted to have "parity bits", meaning that reading garbage values as int could have undefined behavior. In practice, I don't think any implementation actually does have trap representations of int, and I doubt that any will check for mis-aligned pointers if you just read the pointer value -- although they might if you dereference it. But C programmers are nothing if not cautious.
What is garbage value?
When you encounter values at a memory location and cannot conclusively say what these values should be then those values are garbage value for you. i.e: The value is Indeterminate.
Most commonly, when you use a variable and do not initialize it, the variable has an Indeterminate value and is said to possess a garbage value. Note that using an Uninitialized variable leads to an Undefined Behavior, which means the program is not a valid C/C++ program and it may show(literally) any behavior.
Why the particular value exists at that location?
Most of the Operating systems of today use the concept of virtual memory. The memory address a user program sees is an virtual memory address and not the physical address. Implementations of virtual memory divide a virtual address space into pages, blocks of contiguous virtual memory addresses. Once done with usage these pages are usually at least 4 kilobytes. These pages are not explicitly wiped of their contents they are only marked as free for reuse and hence they still contain the old contents if not properly initialized.
On a typical OS, your userspace application only sees a range of virtual memory. It is up to the kernel to map this virtual memory to actual, physical memory.
When a process requests a piece of (virtual) memory, it will initially hold whatever is left in it -- it may be a reused piece of memory that another part of the process was using earlier, or it may be memory that a completely different process had been using... or it may never have been touched at all and be in whatever state it was when you powered on the machine.
Usually nobody goes and wipes a memory page with zeros (or any other equally arbitrary value) on your behalf, because there'd be no point. It's entirely up to your application to use the memory in whatever way you please, and if you're going to write to it anyway, then you don't care what was in it before.
Consequently, in C it is simply not allowed to read a variable before you have written to it, under pain of undefined behaviour.
If you declare a variable without initialising it to a particular value, it may contain a value which was previously assigned by a different program that has since released that piece of memory, or it may simply be a random value from when the computer was booted (iirc, PCs used to initialise all RAM to 0 on bootup because early versions of DOS required it, but new computers no longer do this). You can't assume the value will be zero, for instance.
Garbage value, e.g. in C, typically refers to the fact that if you just reserve memory, but never intialize it, it will hold random values, since it simply is not initialized yet (C doesn't do that for you automatically; it would just be overhead, and C is designed for as little overhead as possible).
The random values in the memory are leftovers from whatever was in there before.
These previous values are left in there, because usually there is not much use in going around setting memory to zero - or any other value - that will later be overwritten again anway. Because for the general case, there is no use in reading uninitialized memory (except if you e.g. want to exploit possible security issues - see the special cases where memory is actually zeroed: Kernel zeroes memory?).
In a program I'm writing, I have an array of accounts(account is a struct I made). I need this visible to all functions and threads in my program. However, I won't know the size it has to be until the main function figures that out. so I created it with:
account *accounts;
and try to allocate space to it in main with this:
number of accounts = 100 //for example
accounts = (account*)malloc(numberOfAccounts * sizeof (account));
However, it appears to be sizing the array larger than it needs to be. For example, accounts[150] exists, and so on.
Is there anything I am doing wrong? How can I get the size of accounts to be exactly 100?
Thanks
You can't do that - malloc() doesn't provide any guarantees about how much memory it actually allocates (except that if it succeeds it will return a pointer to at least as much as you requested). If you access anything outside the range you asked for, it causes undefined behaviour. That means it might appear to work, but there's nothing you can do about that.
BTW, in C you don't need to typecast the returned value from malloc().
Even though it may look like it, accounts[150] does not truly exist.
So why does your program continue to run? Well, that's because even though accounts[150] isn't a real element, it lies within the memory space your program is allowed to access.
C contains no runtime checking of indexes - it just calculates the appropriate address and accesses that. If your program doesn't have access to that memory address, it'll crash with a segmentation fault (or, in Windows terms, an access violation). If, on the other hand, the program is allowed to access that memory address, then it'll simply treat whatever is at that address as an account.
If you try to modify that, almost anything can happen - depending on a wide variety of factors, it could modify some other variables in your program, or given some very unlucky circumstances, it could even modify the program code itself, which could lead to all kinds of funky behavior (including a crash). It is even possible that no side effects can ever be observed if malloc (for whatever reason) allocated more memory than you explicitly requested (which is possible).
If you want to make sure that such errors are caught at runtime, you'll have to implement your own checking and error handling.
I can't seem to find anything wrong with what you provide. If you have a struct, e.g.:
struct account{
int a,b,c,d;
float e,f,g,h;
}
Then you can indeed create an array of accounts using:
struct account *accounts = (struct account *) malloc(numAccounts * sizeof(account));
Note that for C the casting of void* (retun type of malloc) is not necessary. It will get upcasted automatically.
[edit]
Ahhh! I see your problem now! Right. Yes you can still access accounts[150], but basically what happens is that accounts will point to some memory location. accounts[150] simply points 150 times the size of the struct further. You can get the same result by doing this:
*(accounts + 150), which basically says: Give me the value at location accounts+150.
This memory is simply not reserved, and therefore causes undefined behavior. It basically comes down to: Don't do this!
Your code is fine. When you say accounts[150] exits do you mean exits or exists?
If your code is crashing when accessing accounts[150] (assuming numberOfAccounts = 100) then this is to be expected you are accessing memory outside that you allocated.
If you meant exists it doesn't really, you are just walking off the end of the array and the pointer you get back is to a different area of memory than you allocated.
Size of accounts is exacly for 100 structures from malloc result pointer starts if this address is non-zero.
Just because it works doesn't mean it exists as part of the memory you allocated, most likely it belongs to someone else.
C doesn't care or know that your account* came from malloc, all it knows is that is a memory pointer to something that is sizeof(account).
accounts[150] accesses the 150th account-sized object from the value in the pointer, which may be random data, may be something else, depending on your system it may even be your program.
The reason things seem to "work" is that whatever is there happens to be unimportant, but that might not always be the case.