"One of the best programming skills you can have is knowing when to walk away for awhile."
Of course it will segfault if it has 2 or less characters, it means it would have reached the end of that 4Kib, it's just usually a null byte would normally be encountered before that happens, however I did forget about the garbage output and the memory corruption
Nope, it's not a problem.
If "*(str + 1)" is the null terminator (which is will be if the string is "a"), then "*(str)" will not be equal to it, so then "(*(str) == *(str + 2))" will not be executed. Therefore there is no access to "*(str + 2)", and its contents don't matter.
Try it:
And you'll get something like this:Code:#include <stdio.h> #include <stdlib.h> int main ( void ) { char s[2] = { 1, 2 }; char *p = s; int count; // Printing all bytes from s to the end of the page. // NO SEGFAULT! count = 17; printf ( "%p:", p ); for ( ; ( size_t ) p & 0xfff; p++ ) { if ( ! --count ) { printf ( "\n%p:", p ); count = 16; } printf ( " %02hhx", *p ); } putchar ( '\n' ); }
Maybe, if I try to read one more byte there'll be a segfault (if that page is not mapped).Code:0x7ffcf2145dd6: 01 02 00 87 10 d2 f3 55 02 dd c0 e7 46 56 bd 55 0x7ffcf2145de6: 00 00 97 cb 3c 72 06 7f 00 00 01 00 00 00 00 00 0x7ffcf2145df6: 00 00 c8 5e 14 f2 fc 7f 00 00 00 80 00 00 01 00 0x7ffcf2145e06: 00 00 fa e6 46 56 bd 55 00 00 00 00 00 00 00 00 0x7ffcf2145e16: 00 00 ae 43 90 f2 8b 6a cf 56 f0 e5 46 56 bd 55 0x7ffcf2145e26: 00 00 c0 5e 14 f2 fc 7f 00 00 00 00 00 00 00 00 0x7ffcf2145e36: 00 00 00 00 00 00 00 00 00 00 ae 43 f0 86 2e 22 0x7ffcf2145e46: 4c 02 ae 43 8e ab 7f 22 b9 03 00 00 00 00 fc 7f 0x7ffcf2145e56: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7ffcf2145e66: 00 00 33 c7 7a 72 06 7f 00 00 38 26 79 72 06 7f 0x7ffcf2145e76: 00 00 aa 2d 0a 00 00 00 00 00 00 00 00 00 00 00 0x7ffcf2145e86: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7ffcf2145e96: 00 00 f0 e5 46 56 bd 55 00 00 c0 5e 14 f2 fc 7f 0x7ffcf2145ea6: 00 00 1a e6 46 56 bd 55 00 00 b8 5e 14 f2 fc 7f 0x7ffcf2145eb6: 00 00 1c 00 00 00 00 00 00 00 01 00 00 00 00 00 0x7ffcf2145ec6: 00 00 f2 80 14 f2 fc 7f 00 00 00 00 00 00 00 00 0x7ffcf2145ed6: 00 00 f6 80 14 f2 fc 7f 00 00 0c 81 14 f2 fc 7f 0x7ffcf2145ee6: 00 00 f8 86 14 f2 fc 7f 00 00 13 87 14 f2 fc 7f 0x7ffcf2145ef6: 00 00 35 87 14 f2 fc 7f 00 00 4a 87 14 f2 fc 7f 0x7ffcf2145f06: 00 00 62 87 14 f2 fc 7f 00 00 79 87 14 f2 fc 7f 0x7ffcf2145f16: 00 00 8a 87 14 f2 fc 7f 00 00 95 87 14 f2 fc 7f 0x7ffcf2145f26: 00 00 b5 87 14 f2 fc 7f 00 00 d4 87 14 f2 fc 7f 0x7ffcf2145f36: 00 00 e8 87 14 f2 fc 7f 00 00 2e 88 14 f2 fc 7f 0x7ffcf2145f46: 00 00 41 88 14 f2 fc 7f 00 00 66 88 14 f2 fc 7f 0x7ffcf2145f56: 00 00 8a 88 14 f2 fc 7f 00 00 95 88 14 f2 fc 7f 0x7ffcf2145f66: 00 00 be 88 14 f2 fc 7f 00 00 f4 88 14 f2 fc 7f 0x7ffcf2145f76: 00 00 08 89 14 f2 fc 7f 00 00 19 89 14 f2 fc 7f 0x7ffcf2145f86: 00 00 42 89 14 f2 fc 7f 00 00 51 89 14 f2 fc 7f 0x7ffcf2145f96: 00 00 68 89 14 f2 fc 7f 00 00 7a 89 14 f2 fc 7f 0x7ffcf2145fa6: 00 00 9b 89 14 f2 fc 7f 00 00 f1 89 14 f2 fc 7f 0x7ffcf2145fb6: 00 00 24 8a 14 f2 fc 7f 00 00 46 8a 14 f2 fc 7f 0x7ffcf2145fc6: 00 00 5b 8a 14 f2 fc 7f 00 00 70 8a 14 f2 fc 7f 0x7ffcf2145fd6: 00 00 85 8a 14 f2 fc 7f 00 00 ac 8a 14 f2 fc 7f 0x7ffcf2145fe6: 00 00 bf 8a 14 f2 fc 7f 00 00 d2 8a 14 f2 fc 7f 0x7ffcf2145ff6: 00 00 e7 8a 14 f2 fc 7f 00 00
Declare s[] as global and test it again... different addresses, but no segfault!
How an array 2 bytes long "means it would have reached the end of that 4 KiB"?
Last edited by flp1969; 04-05-2019 at 02:13 PM.
Well when it doesn't segfault on memory beyond the actual string length then it will continue until it hits either a null byte or it tries to go beyond addressable memory (accept where it clamps which is instead an inifinite loop) which is where it would then segfault. Just because 99.9% of the time it encounters that null byte does not mean it will always encounter that null byte which means when it does eventually reach the last 2 characters (0xEFFFFFFF) then it will finally crash, possibly cause a BSOD too.
This is not how it works... It will segfault when trying to make an access to memory not mapped on the page tables. Maybe you wanted to say "if the 2 bytes array is at the end of a page AND the next page is not mapped, it will segfault".
The same goes to modes with no paging enabled (i386). If you try to access memory outside of the limit of de descriptor references by the selector, segfault.
Of course, there will be segfault also if the privileges rules are broken (cpl=3 or rpl=3 cannot reference memory described with dpl=0, for instance).
The only garanteed address that WILL segfault is 0 (NULL pointer). The other ones, depends on the memory mapping.
Last edited by flp1969; 04-05-2019 at 02:35 PM.
You guys are going down a rabbit-hole tangent of a problem that doesn't exist. With an expression like if(*(str) == *(str + 1) && (*(str) == *(str + 2))), no memory is accessed that shouldn't be. It works perfectly in all cases in the context of the program in post #11, even when the memory beyond the string is filled with dragons.
Nope... consider we have an string big enough:
And, at some point str is pointing to the last char (the last 'f'). So (str+1) will point to the NUL char, but (str+2) will point to garbage...Code:char s[] = "abcdeeeff";
Ok... I've got sideways explaining why segfault MAY not happen, but this is a forum, isn't it? We are supposed to discuss things, isn't it?
You have to remember that the && operator is "short-circuiting", which means that it will not evaluate the right-hand operand if the result of the whole expression is known based on the left-hand operand. That is, if the left-hand operand is false, then there's no point in evaluating the right-hand operand (the same happens with the || operator too, except the right-hand operand is evaluated only when the left-hand operand is false).
In the "replace" function in post #11, *(str) == *(str + 1) is false when (str+1) points at the NUL character, so the right-hand expression (*(str) == *(str + 2)) will not be evaluated.
Edit to add: this is why it's safe to do something like if (p && *p == '\0')... *p is evaluated only if p is not a null pointer.
Last edited by christop; 04-05-2019 at 02:59 PM.
@awsdert, Another example... when you free() a block of memory previously allocated with malloc() it is an error to access that particular address. But it doesn't mean you'll get an segfault. Because it is impossible to allocate less than 4 KiB of memory with paging enabled. If the block isn't occupying the entire page, there is room for malloc() to "allocate" another block in this same page for other blocks. So, this code probably will not segfault:
Compiling and running:Code:#include <stdio.h> #include <stdlib.h> int main ( void ) { char *p; puts( "Allocating 100 bytes on heap." ); if ( ! ( p = malloc ( 100 ) ) ) { fputs( "ERROR allocating 100 bytes. Aborted.\n", stderr ); return EXIT_FAILURE; } printf( "100 bytes block allocated at %p.\n", p ); free( p ); printf( "Previously allocated block freed.\n" "Access to memory at %p, probably will %ssegfault!\n", p, (size_t)p & 0xfff ? "NOT " : "" ); // this should be illegal! *p = '\0'; printf( "See? I wrote a 0 byte at %p. Nothing happens!\n", p ); return EXIT_SUCCESS; }
Code:$ cc -o test test.c $ ./test Allocating 100 bytes on heap. 100 bytes block allocated at 0x560873087670. Previously allocated block freed. Access to memory at 0x560873087670, probably will NOT segfault! See? I wrote a 0 byte at 0x560873087670. Nothing happens!