7
$\begingroup$

Pointers are variables that store a memory address. They allow the programmer to directly deal with memory which can be used to pass variables by reference or even for performance reasons.

However, common programming advice includes not using pointers while coding for reasons such as dangling pointers, memory leaks, or just the mental overhead that comes from using pointers. I don't even have to mention the 'Billion Dollar Mistake' that comes from null pointers.

There are of course languages such as C and C++ that feature pointers. However, even newer languages such as Go and [unsafe] Rust as well as lower level assembly languages allow programmers to directly use pointers. In Java, all objects are of reference types, but the user can't access the memory addresses directly.

So my question basically is, what are the pros and cons of allowing direct access to pointers and memory addresses?

$\endgroup$
3
  • $\begingroup$ Pointers in Go are more similar to nullable references. You can't do pointer arithmetic. $\endgroup$
    – alephalpha
    Jun 1, 2023 at 10:28
  • 2
    $\begingroup$ Note that you still can access memory addresses of objects in Java (and other JVM languages) using sun.misc.Unsafe. $\endgroup$
    – Seggan
    Jun 1, 2023 at 14:32
  • 1
    $\begingroup$ Even very high level languages like python do allow raw pointer accesses though you have to jump through a lot more hoops $\endgroup$
    – mousetail
    Jun 1, 2023 at 14:35

4 Answers 4

10
$\begingroup$

Because a lot of low level APIs work with pointers. For example in openGL you might use a function like this: void glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage); Note you need to pass data in as a void pointer and the other arguments define how this needs to be interpreted.

This style of ABI call is very typical of low level or OS interfaces. Syscalls by nature can only read data in a very primitive way so it's hard to pass any more complex types than pointers.

You could of course write an abstraction layer to avoid working with pointers, and most languages do do that, but you can't write an abstraction layer for every possible API, even those on obscure platforms and operating systems. Programmers might also want to interface with libraries that use such calling conventions.

For this reason, it's nice to at least have the option to use raw pointers, if only so you can wrap these dangerous functions in a safer interface. There would be no way to use these calls at all without such a feature.

$\endgroup$
4
$\begingroup$

High level abstractions are all well and good, until they aren't. Sometimes you need to drop down to a lower level. Either to interact with code that was written in a different language, because you don't want to accept the performance cost of the high level abstractions, or because you are the person creating the high level abstractions.

C has for better or worse became the lingua-franca for low level interaction between code written in different languages.

Language designers have broadly two choices to accommodate the requirements for lower-level programming and/or for interacting with existing code written in other languages.

  1. Provide low-level programming constructs within the language.
  2. Provide an API for interacting with the language's data structures and functions from low level code written in another language (usually C or C++).

I think which approach is taken depends very much on how the language is intended to be implemented. A language intended to be compiled to native code is likely to favour option 1, while a language intended to be interpreted is more likely to favour option 2.

$\endgroup$
3
$\begingroup$

The crucial thing about pointers is that certain algorithms and data structures can't be expressed without them.

Any program that branches requires pointers.

And any data structure that is variable-length or recursive requires a system of indirection to express, of which pointers are a native implementation.

There are two different purposes or usages associated with pointers - pointers to code (which you call or jump with), and pointers to data (which you dereference with or follow).

There a few useful computer applications that can do without branching or variable-length data structures, so all languages have the concept of pointers at least implicitly.

A disadvantage of not allowing raw pointer access, is that the workings of such a language can't be explained in its own terms, and therefore such languages with concealed or implied workings are poor for educational use.

Another disadvantage of not allowing direct pointer access, is that patterns which fundamentally require indirection in a way which steps outside any facility offered by the language, end up being reproduced in a way that leads to source code that is laborious and non-standard, is inefficient to execute, and which are just as prone to crashes and implementation errors.

Some typical advantages of not allowing direct pointer access is enabling compiler optimisations. This is because the compiler writer controls all the patterns of how pointers can be used, and therefore can optimise in ways that might not be possible if the language user could use pointers freely.

Similarly, certain language features can be guaranteed to work, in cases where they might often be broken if direct pointer access were allowed.

Another perceived advantage to disallowing direct pointer access is safety. The thinking is that you can't get it wrong if you can't do it.

I'm a bit more sceptical of this last advantage though. It's more like saying an electrician can't shock himself if he doesn't have access to the wiring - the point is to achieve the benefits of what electricians do with lesser risk, not to simply avoid the risks by avoiding the activity altogether (and implicitly avoiding the benefits).

I think the problem sometimes just gets moved around this way. The "safe" language is clearly more safe against a particular fault, but is then less safe against other hazards (including how either complicated workarounds, or simplified and limited solutions, become more liable to contain faults).

$\endgroup$
8
  • $\begingroup$ Any program that branches requires pointers?? $\endgroup$
    – chrysante
    Sep 19, 2023 at 14:12
  • $\begingroup$ @chrysante, indeed, at least implicitly. Many languages conceal this, but you can't fundamentally understand what computers do without pointers. $\endgroup$
    – Steve
    Sep 19, 2023 at 15:17
  • 1
    $\begingroup$ @chrysante, to branch there must be a destination, and that destination must be stored as a pointer (to which the instruction pointer register will be set). Pointers aren't an implementation detail - they're a computer implementation of the general concept of indirection. A language which doesn't acknowledge them is limited in terms of even explaining its own implementation, let alone anything else. $\endgroup$
    – Steve
    Sep 19, 2023 at 16:10
  • 1
    $\begingroup$ It’s very typical for an architecture to have an instruction pointer natively, but I have worked on a chip where there wasn’t one. Of course, I also designed abstractions to make it look like there was, because it’s a familiar programming model. But for example loops and branching didn’t necessarily involve pointers at all, if they didn’t need to fetch code from memory. $\endgroup$
    – Jon Purdy
    Sep 21, 2023 at 20:15
  • 1
    $\begingroup$ @JonPurdy, it certainly sounds unconventional, but aren't we simply dealing with pointers to page-addressable code, rather than byte-addressable? It seems to me that even though implementation details can vary, there is something essential and irreducible about storage being addressable (at some level of granularity), and about the processor having to have some facility for expressing (and altering) the storage address from which it is currently fetching and executing instructions. $\endgroup$
    – Steve
    Sep 22, 2023 at 7:40
1
$\begingroup$

The ability to pass around references to objects is fundamental in many programming languages. I'm assuming the question is referring to the concept of "pointers" more broadly, encompassing not merely pointers that identify "root-level" objects or allocations that an implementation has created, but also pointers that may identify other kinds of regions of storage.

Pointer manipulations, often including the ability to convert pointers to integers, perform computations, and then convert the resulting integers back to pointers, provide a means by which human knowledge related to regions of memory and what may be done with them, can be applied to the process of instructing computers to do those things.

As a simple example, on systems that can only accommodate one program in memory at once (which used to be common in many microcomputer systems, and remains common in the embedded systems realm), any memory that isn't being used by the current program won't be used by anything at all until the current program exits. Thus, it would be useful to allow a program to, on startup, identify all space that isn't used for anything else and treat it as a blob the program can use as it sees fit, adapting itself to the amount of space that happens to be available.

On a typical embedded systems compiler, this may be accomplished by instructing the linker to automatically generate a symbol that points to just past the last object which it placed near the bottom of storage, and another that points to the first object that it placed near the top. Any storage between these two addresses then be used as the program sees fit. The C Standard doesn't allow for the possibility that a programmer might know anything about the relationship between two symbols that--from the compiler's point of view--would have nothing special about them, but when using implementations that are designed to be suitable for systems programming, a programmer can write memmory-allocation functions which manipulate pointers to access storage within this area.

When enabling some compilers' optimizations, however, there can be a problem. Suppose a program does something like:

extern unsigned char heap_start[], heap_end[];

void heap_init(void)
{
  for (unsigned char *p=heap_start; p != heap_end; p++)
    *p=0;
  p[-1]=255;
}
int sneaky_test(int x)
{
  heap_start[x]=0;
  heap_init();
  return heap_start[x];
}

If sneaky_test happens to be passed a value one less than the displacement between heap_end and heap_start, then heap_init will set heap_start[x] to 255, and the sneaky_test function should return 255. Some compilers, however, might recognize that because the pointer p used in the statement p[-1]=255; will always equal heap_end, that expression may be replaced with heap_end[-1]=255;, and since nothing that happens between heap_start[x]=0 and return heap_start[x] sets any storage that could be derived from heap_start to anything other than zero, the compiler may generate code that always returns zero.

Unfortunately, the way free compilers, or compilers based upon them, have evolved, it is often much easier to determine the sequence of machine operations that would need to be done to perform a task, and even to write source code that will on any given version of the compiler yield a machine code program that, if inspected, would be found to perform that sequence of operations, than to write source code that would, by specification, produce a usable machine code program. In most cases where a programmer might need to perform operations like the above, a compiler wouldn't happen to find "optimizing" transforms that would allow it to skip steps that are essential to the task at hand, even if it would have performed the transformations if it had found them, and thus code which would be usefully processed by today's compiler might fail with future compilers that "optimize" more aggressively.

$\endgroup$
3
  • 1
    $\begingroup$ I don't fully understand this answer. What advantage of pointer-use is being described here? $\endgroup$ Sep 16, 2023 at 20:36
  • $\begingroup$ @DavidYoung: The ability to create and use pointers whose addresses have no meaning understood by the language implementation makes it possible to treat regions of address space that are understood by the programmer but not the implementation in the same way as one would treat storage that was e.g. allocated on an implementation-managed heap. A program that uses such techniques can continuously track on how much memory is available, and avoid starting operations which the program wouln't have enough memory to complete. $\endgroup$
    – supercat
    Sep 17, 2023 at 16:05
  • $\begingroup$ @DavidYoung: I think (now) third paragraph clearly describes a "pro" of a language allowing pointers to be manipulated in ways an implementation couldn't be expected to validate. The rest of the answer concerns a limitation/downside, which requires a bit more background info to explain, but whose essence I guess could be described as "Constructs which manipulate pointers in ways whose implications an implementations can't fully understand may run afoul of implementations which are designed around the assumption that they can understand all implications of everything done with pointers." $\endgroup$
    – supercat
    Sep 17, 2023 at 16:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .