Write to dynamic two dimension array outside the range without Segmentation fault

  • Thread starter lovecreatesbea...
  • Start date
B

Barry Schwarz

Isn't the approach in my code example flexible enough? Isn't it correct?

No, it's not. For one,

char (*array)[COL];

Is errorneous. It is different from:

His definition is not erroneous. He defines a pointer to an array and
uses it to reference the arrays it points to.
char *array[COL];

You should know that [] precedes the * operator. Insted of creating an
array of pointers to characters, you created a pointer to an array of
characters. See the difference?

While what you say is true, it has nothing to do with his code.


Remove del for email
 
B

Barry Schwarz

This code snippet is an exercise on allocating two dimension array
dynamically. Though this one is trivial, is it a correct one?
Furthermore, when I tried to make these changes to the original
example:

for (i = 0; i < ROW + 2; ++i){ /*line 14*/
strcpy(array, "C! C!"); /*line 15*/

Apparently, the modified code wrote to the memory unit outside the
allocated array, but the program ran well and there was no Segmentation
fault. Thank you.


snip code

One of the unluckiest manifestations of undefined behavior is to
appear to work as intended.


Remove del for email
 
B

bwaichu

Let me clarify what I had written before. I had said that strcpy would
not nul terminate. Now, the fuction in theory should nul terminate.
This is not true if the space being written to is smaller. The same
holds true for strncpy.

Here's some example code, where strcpy does not nul terminate. I have
left out the test for calloc's (replaced the OP's malloc) success, and
I have left out the free'ing as the OP had done in their code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>

#define COL 4
#define ROW 4

int
main(void) {

int i;
char (*string)[COL] = calloc(ROW, sizeof *string);

for (i = 0; i < ROW; i++) {
strcpy(string, "hello");
printf("%s\n", string);
}
exit(EXIT_SUCCESS);
}

If I run this, it will print out hello four times. But that doesn't
mean it works. COL can only hold 3 characters plus a nul per row.

So when I run this through gdb, I see:

Breakpoint 1 at 0x4008ce: file string.c, line 20.
(gdb) run
Starting program: /home/user/string
hello
hello
hello
hello

Breakpoint 1, main () at string.c:20
20 exit(EXIT_SUCCESS);
(gdb) p *string
$1 = "hell"
(gdb) p/x string[0]
$2 = {0x68, 0x65, 0x6c, 0x6c}
(gdb)

As you can see, there is no nul terminator. The space was only large
enough to hold 4 characters. The remaining characters overwrote the
buffer space. But the program still appeared to work as seen by the
hello's printing.

I hope this explanation is better than just saying that strcpy does not
nul terminate. The changes suggested in the OP's message would write
to unallocated space. The for loop would try to strcpy to ROW's that
haven't been allocated.
 
R

Richard Bos

Coos said:
You mis-read my comment. The OP only allocated array[0], so the
designation
array[1] through array[3] has not be allocated. Therefore, when you
strcpy, there is no way you can nul terminate since the designation is
smaller than the string being copied.

You write nonsense here, strcpy _always_ terminates with a null, even if
the string to copy has zero length. memcpy won't copy anything in this
case. The allocation may have been bad, but that is not my point.

The function strcpy will attempt to nul terminate, but if the space
isn't big enough, it might seg fault.

The same is true for any other character that strcpy() writes. If you
wanted to say that strcpy() has undefined behaviour if it is made to
write to memory that has not been properly allocated, that's what you
should have written. This claptrap about null termination is just that:
balderdash.

Richard
 
B

Barry Schwarz

Let me clarify what I had written before. I had said that strcpy would
not nul terminate. Now, the fuction in theory should nul terminate.
This is not true if the space being written to is smaller. The same
holds true for strncpy.

If you use any function to move data into a space too small for that
data, you invoke undefined behavior.
Here's some example code, where strcpy does not nul terminate. I have
left out the test for calloc's (replaced the OP's malloc) success, and
I have left out the free'ing as the OP had done in their code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>

#define COL 4
#define ROW 4

int
main(void) {

int i;
char (*string)[COL] = calloc(ROW, sizeof *string);

for (i = 0; i < ROW; i++) {
strcpy(string, "hello");
printf("%s\n", string);
}
exit(EXIT_SUCCESS);
}

If I run this, it will print out hello four times. But that doesn't


If you run this, you invoke undefined behavior. It doesn't matter
what the results are on your system because we are no longer in the
realm of C.
mean it works. COL can only hold 3 characters plus a nul per row.

You meant string, not COL. In this particular case, string points to
an allocated block of memory which is 16 bytes. When i is 0, the
strcpy and the printf will reference bytes 0 through 5, When i is 1,
they use bytes 4 through 9. When i is 2, They use bytes 8 through 13.
All these operations are well defined. When i is 3, they attempt to
use bytes 12 through 17. This is undefined behavior.



Remove del for email
 
C

Chris Torek

Let me clarify what I had written before. I had said that strcpy would
not nul terminate. Now, the fuction in theory should nul terminate.
This is not true if the space being written to is smaller.

Well, yes and no:

char buf[4]; /* can hold "abc" */

strcpy(buf, "supercalifragialisticexpialidocious"); /* ERROR */

The effect of this call to strcpy() is undefined, because the
call "wants" to write 36 "C bytes" ("char"s) into a four-character
array.

In practice, on most real machines, it really *does* write all 36
bytes (35 letters plus '\0' byte) into the four-character array,
overwriting 32 other bytes. Subsequent behavior depends on what
was in those "other" bytes, and how important they were. Of course,
since the behavior is undefined, the C Standards say nothing about
what has to happen, so whatever *does* happen is the programmer's
problem, not the implementation's.

In the case when what *does* happen is "the program continues to
run", if you stop the program with some external agent and inspect
the array named "buf", it is true that it will not hold a '\0'-terminated
C string. Instead, it will hold the first four characters of the
copied string -- in this case 's', 'u', 'p', and 'e'. This is
precisely what you see when you use your debugger:

[I need to quote the entire code, or change it; for time reasons on
my part, I will just quote]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>

#define COL 4
#define ROW 4

int
main(void) {

int i;
char (*string)[COL] = calloc(ROW, sizeof *string);

for (i = 0; i < ROW; i++) {
strcpy(string, "hello");
printf("%s\n", string);
}
exit(EXIT_SUCCESS);
}

If I run this, it will print out hello four times. But that doesn't
mean it works. COL can only hold 3 characters plus a nul per row.

So when I run this through gdb, I see:

Breakpoint 1 at 0x4008ce: file string.c, line 20.
(gdb) run
Starting program: /home/user/string
hello
hello
hello
hello

Breakpoint 1, main () at string.c:20
20 exit(EXIT_SUCCESS);
(gdb) p *string
$1 = "hell"
(gdb) p/x string[0]
$2 = {0x68, 0x65, 0x6c, 0x6c}
(gdb)

As you can see, there is no nul terminator. The space was only large
enough to hold 4 characters. The remaining characters overwrote the
buffer space. But the program still appeared to work as seen by the
hello's printing.


And in fact, what happened on your machine (which I can predict
due to my Amazing Psychic Abilities :) ) is that calloc(4,4)
actually obtained more than 16 bytes, and each of the strcpy()
calls put six bytes into the corresponding four-byte region, with
the "extra two bytes" writing over the next array entry (when i
\elem {0,1,2}) or some of the "spare" bytes calloc() got (when
i==3). The printf() calls accessed all six bytes of each four-byte
region (including the two that extend past the region). The
debugger, however, is much smarter than the C runtime library, and
even if you print string[3], it will only access the "proper four"
of the six bytes written into corresponding four-byte region.

Note also that:

char buf16[16];
strcpy(&buf16[0], "hello");
strcpy(&buf16[4], "hello");

is well-defined, and puts the string "hellhello" into the 16-byte
buffer "buf16". The first strcpy() does in fact write a complete
string; the second strcpy() then overwrites part of the first,
writing a new complete string that makes the previous string
longer.

If you then do:

printf("%.4s\n", &buf[0]);

the output will be "hell\n", not "hellhello\n" -- and gdb essentially
does just this.
I hope this explanation is better than just saying that strcpy does not
nul terminate.

It just goes to show that, when there is undefined behavior,
*anything* can happen, including "it seems to work, but the debugger
-- which does different things from the C library -- shows different
results".
 
B

bwaichu

Barry said:
You meant string, not COL. In this particular case, string points to
an allocated block of memory which is 16 bytes. When i is 0, the
strcpy and the printf will reference bytes 0 through 5, When i is 1,
they use bytes 4 through 9. When i is 2, They use bytes 8 through 13.
All these operations are well defined. When i is 3, they attempt to
use bytes 12 through 17. This is undefined behavior.

You are explanation is far better than mine. Is the right answer all
ways undefined behavior when the program is written in a way, where the
C programming language is no longer clear as to what happens?
 
B

Barry Schwarz

You are explanation is far better than mine. Is the right answer all
ways undefined behavior when the program is written in a way, where the
C programming language is no longer clear as to what happens?

The standard identifies several different types of behavior: defined,
unspecified, undefined, implementation defined, and possibly others.

Clarity on the other hand is pretty subjective, confirmed by many of
the wandering threads in this group.


Remove del for email
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,999
Messages
2,570,243
Members
46,836
Latest member
login dogas

Latest Threads

Top