Decrement a given pointer.

Discussion in 'C Programming' started by Michael Press, Jul 9, 2013.

  1. Given a pointer, p, can I set pm1 = p-1 and use pm1
    without worrying that an implementation will object
    or do other than what one expects? The idea is to
    get offset one arrays, e.g.,

    void
    f(int *p, int l)
    {
    int i;
    int t;
    int *pm1 = p - 1;

    for(i = 1; i <= l; i++)
    t = pm1;
    }

    int a[] = {1, 2, 3};
    int na = sizeof a / sizeof *a;

    void
    doit(void)
    {
    int *b = a;

    f(a, na);
    f(b, na);
    }
     
    Michael Press, Jul 9, 2013
    #1
    1. Advertisements



  2. No. Given a pointer to an array element, you can safely construct a
    pointer to any element of the array. You can also safely construct a
    pointer just past the end of the array, but you can't dereference it.
    Using pointer arithmetic to construct a pointer outside the bounds of
    the array has undefined behavior. (A single object is treated as a
    one-element array for purposes of pointer arithmetic.)

    It's fairly likely to work on most systems, but it's not guaranteed.

    I seem to recall that the book "Numerical Recipes in C" used this
    technique to translate Fortran code into C.
     
    Keith Thompson, Jul 9, 2013
    #2
    1. Advertisements

  3. Michael Press

    Joe Pfeiffer Guest

    <snip>

    Keith has already given what I expect is the right answer to your
    question, but I'd go on to ask "why?". Unless there's a *really* good
    reason, you should simply use the language as designed.

    Having said that, I'll mention that I have occasion to use what amount
    to offset 1 arrays on a current project: I'm obtaining altimeter data
    from an altimeter that has a parameter that goes from 1 to 9; it seems
    less error-prone to me to use a 10 element array and just waste element
    0 than to mess with macros or other code to add and subract 1 from an
    index in multiple places. But you'll notice that this approach to it
    doesn't depend on tricky code having undefined behavior do the right
    thing.
     
    Joe Pfeiffer, Jul 9, 2013
    #3
  4. Michael Press

    Siri Cruise Guest

    Some CPUs use address registers and check address validity when the address
    computed rather than waiting for address load. If the array is at the beginning
    some memory partition, these kinds of CPUs can get an address fault.
     
    Siri Cruise, Jul 9, 2013
    #4
  5. Michael Press

    Eric Sosman Guest

    This is Question 6.17 on the comp.lang.c Frequently
    Asked Questions (FAQ) page at <http://www.c-faq.com/>.
     
    Eric Sosman, Jul 9, 2013
    #5
  6. Michael Press

    Ian Collins Guest

    Not in the context of the OP, the address p-1 was never dereferenced.
     
    Ian Collins, Jul 9, 2013
    #6
  7. Michael Press

    Eric Sosman Guest

    Even computing it (trying to compute it) yields undefined
    behavior. FAQ 6.17.
     
    Eric Sosman, Jul 9, 2013
    #7
  8. Michael Press

    Siri Cruise Guest

    It's not guarenteed to work because some CPUs validate addresses on computation
    before dereference.
     
    Siri Cruise, Jul 9, 2013
    #8
  9. Siri Cruise said that the validity of the address is checked when it's
    computed, not when it's dereferenced, so yes, that kind of CPU would get
    an address fault.

    (Which is consistent with my statement, since most CPUs don't do that.
    Still, I certainly don't recommend counting on that.)
     
    Keith Thompson, Jul 9, 2013
    #9
  10.  
    glen herrmannsfeldt, Jul 9, 2013
    #10
  11. Michael Press

    Siri Cruise Guest

    Siri Cruise said that the validity of the address is checked when it's
    computed, not when it's dereferenced, so yes, that kind of CPU would get
    an address fault.

    (Which is consistent with my statement, since most CPUs don't do that.
    Still, I certainly don't recommend counting on that.)[/QUOTE]

    An alternative is something like
    int A_[m,n];
    #define A(j,k) A_[(j)-1,(k)-1]
     
    Siri Cruise, Jul 9, 2013
    #11
  12. Michael Press

    James Kuyper Guest

    On 07/08/2013 10:44 PM, Siri Cruise wrote:
    ....
    That's equivalent to

    int A_[n];
    #define A(j,k) A_[(k)-1]

    Were you thinking of Fortran?
     
    James Kuyper, Jul 9, 2013
    #12
  13. AS/400 is commonly cited here as an example of such a system.
    You seem to be assuming that the wrapped pointer will not exceed the
    segment limit and generate an exception. That is probably true on x86
    systems, where the segment limit is almost always (unsigned)(-1), but
    probably not on other segmented systems.
    True, but the selector would remain valid if the original pointer were
    valid. i286 doesn't validate the offset part, which is likely to be
    invalid in this case, before a load or store is performed; doing so
    would be impossible since it doesn't have dedicated address registers.

    S
     
    Stephen Sprunk, Jul 9, 2013
    #13
  14. (snip, someone wrote)
    Yes, they might do it. Do they have a C compiler?
    I am not sure at all what it does in huge mode, I never used that.
    In large mode, the offset is in an ordinary register, and will
    wrap back again before the dereference.

    The segment selector has to exist when the value is loaded into
    a segment register, but the offset isn't checked until an actual
    dereference (load or store).

    In 32 bit protected mode, I believe it is usual to set the limit to,
    as you note, (unsigned)(-1), but in 16 bit mode, no. In 32 bit
    mode, you have the PMMU to validate addresses, in 16 bit the only
    validation is the segment selector limit.

    I believe it is usual to do pointer assignment without loading
    the pointer into a segment register. As above, loading the
    segment register would require the segment be valid.
    Well, there are instructions that load both a segment register and
    another register with a segment/offset pair. In that case it could
    be done, but I don't believe it is done. It would be extra work
    that isn't necessary.

    I don't know AS/400 addressing enough to know if it is necessary
    to validate early.

    -- glen
     
    glen herrmannsfeldt, Jul 9, 2013
    #14
  15. In huge mode, it should work, but not in large mode.
    Huge mode decrements the segment selector when the offset wraps.
    (Much extra code to do that, so I try not to use it.)

    The segment selector will be invalid, but I believe the arithmetic
    is not done in segment registers. (There is no decrement operation
    on segment registers.)
    Again, large but not huge. Huge mode has to compare both the segment
    and offset of the pointer. Large mode only the offset.
    The 68000 is a 16 bit processor, but able to address more than 64K.
    I don't remember quite how they did it.

    Some time before the 68020, I used a 68010 system with a custom MMU.

    -- glen
     
    glen herrmannsfeldt, Jul 9, 2013
    #15
  16. Michael Press

    Siri Cruise Guest

    Yes, they might do it. Do they have a C compiler?[/QUOTE]

    It doesn't matter whether you think this is the result of stupid design. What
    matters is some vendor with enough influence with ANSI got this caveat written
    into the C standard. Code that violates it may run on 99% of all machines; but
    it is still code not guaranteed for 100%. If you're happy with that and so are
    your customers, go for it. I write code that only runs on Unix or even just
    MacOSX. My customers pay for that, so I'm fine with being nonstandard.

    I added my comment simply to explain why such an odd rule exists. I once worked
    on CDC computers with address registers so I happen to be aware of these issues.
    I have no comment on whether this is a good idea.

    I avoid the issue by letting array indices go out of bounds instead of pointers,
    such as
    #define A(j,k) A_[(j)-1][(k)-1]
    or
    for (int j=n-1; j>=0; j--) f(B[j]);
     
    Siri Cruise, Jul 10, 2013
    #16
  17. The m68k is a 32-bit processor, at least in the sense it presented
    32-bit registers and a 32-bit address space to the programmer. The
    first implementation used pairs of 16-bit registers with carry, but that
    was invisible to the programmer; code continued to work as-is when
    ported to later implementations that had true 32-bit registers.

    The m68k had separate address and data registers (to save encoding
    bits), so it was possible for the CPU to fault when loading or
    manipulating an invalid pointer even without dereferencing it.

    S
     
    Stephen Sprunk, Jul 10, 2013
    #17
  18. Indexing into a heap. The top of the heap is heap[1].
    The two subsidiary nodes to heap[k] are
    heap[2 * k] and heap[2 * k + 1].
    I often have arrays that naturally start at 1 and do the same
    as you: waste the array entry at index 0. Sometimes I do not
    have the choice.
    That is why I asked.
     
    Michael Press, Jul 10, 2013
    #18
  19. There are other problems that can happen besides faults
    happening when you form the address.

    Consider this loop to traverse an array of structures backwards:

    struct huge *p;
    struct huge bigarray[MAX];

    /* WRONG! */
    for (p = &bigarray[MAX-1]; p >= &bigarray[0]; p--) {
    ... do something with struct huge
    pointed at by p ...;
    }[/QUOTE]

    So write

    for (int k = MAX; k-- > 0; ) {
     
    Michael Press, Jul 10, 2013
    #19
  20. Michael Press

    Joe Pfeiffer Guest

    Ah, should have thought of that one. While it's not quite as elegant as
    the standard scheme, using heap[2*k + 1] and heap[2*k + 2] works just
    fine with 0-offset arrays and and doesn't involve weird messing with
    pointers.
    And, of course, simply rooting your heap at heap[1] and wasting heap[0]
    works just fine here as well.
     
    Joe Pfeiffer, Jul 10, 2013
    #20
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.