Why did C use the -> operator instead of reusing the . operator?Why did the Bell 103 modem use a data rate of 300 bps?Why did CP/M and MS-DOS used the BIOS drivers instead of their own drivers to access hardware?What is the origin of different styles of assembly language mnemonics?Why do articles use the term “UART” instead of “serial port”?Did any computers use the Z80B?Why did C have the return type before functions?Why did computers use a power supply with a socket?Who established the original F1 desktop BIOS key and why did laptops use a different key?Why green phosphor instead of amber?Why did the IBM 650 use bi-quinary?
What word means to make something obsolete?
Examples of non trivial equivalence relations , I mean equivalence relations without the expression " same ... as" in their definition?
Please, smoke with good manners
Illegal assignment from SObject to Contact
Binary Numbers Magic Trick
Why do Ichisongas hate elephants and hippos?
Do generators produce a fixed load?
What does "rf" mean in "rfkill"?
Counterexample: a pair of linearly ordered sets that are isomorphic to subsets of the other, but not isomorphic between them
Upright [...] in italics quotation
Confused by notation of atomic number Z and mass number A on periodic table of elements
Pawn Sacrifice Justification
You look catfish vs You look like a catfish
Was it really necessary for the Lunar Module to have 2 stages?
Past Perfect Tense
Any examples of headwear for races with animal ears?
Can a creature tell when it has been affected by a Divination wizard's Portent?
TikZ how to make supply and demand arrows for nodes?
Why do TACANs not have a symbol for compulsory reporting?
What are the spoon bit of a spoon and fork bit of a fork called?
How to back up a running remote server?
Can I get candy for a Pokemon I haven't caught yet?
Has any spacecraft ever had the ability to directly communicate with civilian air traffic control?
Cannot populate data in lightning data table
Why did C use the -> operator instead of reusing the . operator?
Why did the Bell 103 modem use a data rate of 300 bps?Why did CP/M and MS-DOS used the BIOS drivers instead of their own drivers to access hardware?What is the origin of different styles of assembly language mnemonics?Why do articles use the term “UART” instead of “serial port”?Did any computers use the Z80B?Why did C have the return type before functions?Why did computers use a power supply with a socket?Who established the original F1 desktop BIOS key and why did laptops use a different key?Why green phosphor instead of amber?Why did the IBM 650 use bi-quinary?
In the C programming language, the syntax to access the member of a structure is
structure
.
member
However, a member of a structure referenced by a pointer is written as
pointer
->
member
There's really no need for two different operators. The compiler knows the type of the left-hand value; if it is a structure, the first meaning is evident. If it is a pointer, the second meaning is evident. Furthermore, .
is far easier to type than ->
. Not only does ->
have more characters to type, on many keyboards one character is unshifted and the other character is shifted, requiring some finger acrobatics. Indeed, many languages based on C allow or use .
in place of ->
.
Why did C use two operators when one would have sufficed?
Update: As this is the Retrocomputing site, it is implied that questions are asked from a historical perspective. To that end, I have added the history tag, and accepted the answer that provided the best historical context.
A few answers took a more speculative approach; a few others raised concerns that became valid later. These are fascinating answers, and I appreciate those who wrote them, but they don't address the history of the decision.
I dislike answering my own questions. However, I did find a paper by Dennis Ritchie that helps to answer this question, and has not been addressed in other answers. I therefore wrote an answer, which you are welcome to examine below.
history c
|
show 10 more comments
In the C programming language, the syntax to access the member of a structure is
structure
.
member
However, a member of a structure referenced by a pointer is written as
pointer
->
member
There's really no need for two different operators. The compiler knows the type of the left-hand value; if it is a structure, the first meaning is evident. If it is a pointer, the second meaning is evident. Furthermore, .
is far easier to type than ->
. Not only does ->
have more characters to type, on many keyboards one character is unshifted and the other character is shifted, requiring some finger acrobatics. Indeed, many languages based on C allow or use .
in place of ->
.
Why did C use two operators when one would have sufficed?
Update: As this is the Retrocomputing site, it is implied that questions are asked from a historical perspective. To that end, I have added the history tag, and accepted the answer that provided the best historical context.
A few answers took a more speculative approach; a few others raised concerns that became valid later. These are fascinating answers, and I appreciate those who wrote them, but they don't address the history of the decision.
I dislike answering my own questions. However, I did find a paper by Dennis Ritchie that helps to answer this question, and has not been addressed in other answers. I therefore wrote an answer, which you are welcome to examine below.
history c
11
You can still write(*structure).member
, if you like that more. (I don't, very probably K&R didn't, either. It is a bit awkward to handle because of C operator precedence, and that might answer your question)
– tofro
Apr 24 at 16:14
21
Just out of curiosity: In your hypothetical language wherea.b
could be interpreted as(*a).b
if a is a pointer-to-struct, would it also be automatically be interpreted as(**a).b
if a is a pointer-to-pointer-to-struct? Just to point out a possible consequence…
– wrtlprnft
Apr 24 at 19:29
30
People complain a lot about pointers being confusing. Imagine adding to that confusion by not knowing if a variable was a pointer or not when reading code.
– JPhi1618
Apr 24 at 19:53
3
Just to note: Wirth's language Oberon does almost exactly this, allowing the Pascal-like^
to be omitted in the expressionsp^.x
andp^[i]
, and using types to disambiguate.
– Mike Spivey
Apr 24 at 20:51
2
Basically the same question on SO: stackoverflow.com/questions/13366083/…
– DaveInCaz
Apr 25 at 18:25
|
show 10 more comments
In the C programming language, the syntax to access the member of a structure is
structure
.
member
However, a member of a structure referenced by a pointer is written as
pointer
->
member
There's really no need for two different operators. The compiler knows the type of the left-hand value; if it is a structure, the first meaning is evident. If it is a pointer, the second meaning is evident. Furthermore, .
is far easier to type than ->
. Not only does ->
have more characters to type, on many keyboards one character is unshifted and the other character is shifted, requiring some finger acrobatics. Indeed, many languages based on C allow or use .
in place of ->
.
Why did C use two operators when one would have sufficed?
Update: As this is the Retrocomputing site, it is implied that questions are asked from a historical perspective. To that end, I have added the history tag, and accepted the answer that provided the best historical context.
A few answers took a more speculative approach; a few others raised concerns that became valid later. These are fascinating answers, and I appreciate those who wrote them, but they don't address the history of the decision.
I dislike answering my own questions. However, I did find a paper by Dennis Ritchie that helps to answer this question, and has not been addressed in other answers. I therefore wrote an answer, which you are welcome to examine below.
history c
In the C programming language, the syntax to access the member of a structure is
structure
.
member
However, a member of a structure referenced by a pointer is written as
pointer
->
member
There's really no need for two different operators. The compiler knows the type of the left-hand value; if it is a structure, the first meaning is evident. If it is a pointer, the second meaning is evident. Furthermore, .
is far easier to type than ->
. Not only does ->
have more characters to type, on many keyboards one character is unshifted and the other character is shifted, requiring some finger acrobatics. Indeed, many languages based on C allow or use .
in place of ->
.
Why did C use two operators when one would have sufficed?
Update: As this is the Retrocomputing site, it is implied that questions are asked from a historical perspective. To that end, I have added the history tag, and accepted the answer that provided the best historical context.
A few answers took a more speculative approach; a few others raised concerns that became valid later. These are fascinating answers, and I appreciate those who wrote them, but they don't address the history of the decision.
I dislike answering my own questions. However, I did find a paper by Dennis Ritchie that helps to answer this question, and has not been addressed in other answers. I therefore wrote an answer, which you are welcome to examine below.
history c
history c
edited yesterday
Dr Sheldon
asked Apr 24 at 15:44
Dr SheldonDr Sheldon
2,40431237
2,40431237
11
You can still write(*structure).member
, if you like that more. (I don't, very probably K&R didn't, either. It is a bit awkward to handle because of C operator precedence, and that might answer your question)
– tofro
Apr 24 at 16:14
21
Just out of curiosity: In your hypothetical language wherea.b
could be interpreted as(*a).b
if a is a pointer-to-struct, would it also be automatically be interpreted as(**a).b
if a is a pointer-to-pointer-to-struct? Just to point out a possible consequence…
– wrtlprnft
Apr 24 at 19:29
30
People complain a lot about pointers being confusing. Imagine adding to that confusion by not knowing if a variable was a pointer or not when reading code.
– JPhi1618
Apr 24 at 19:53
3
Just to note: Wirth's language Oberon does almost exactly this, allowing the Pascal-like^
to be omitted in the expressionsp^.x
andp^[i]
, and using types to disambiguate.
– Mike Spivey
Apr 24 at 20:51
2
Basically the same question on SO: stackoverflow.com/questions/13366083/…
– DaveInCaz
Apr 25 at 18:25
|
show 10 more comments
11
You can still write(*structure).member
, if you like that more. (I don't, very probably K&R didn't, either. It is a bit awkward to handle because of C operator precedence, and that might answer your question)
– tofro
Apr 24 at 16:14
21
Just out of curiosity: In your hypothetical language wherea.b
could be interpreted as(*a).b
if a is a pointer-to-struct, would it also be automatically be interpreted as(**a).b
if a is a pointer-to-pointer-to-struct? Just to point out a possible consequence…
– wrtlprnft
Apr 24 at 19:29
30
People complain a lot about pointers being confusing. Imagine adding to that confusion by not knowing if a variable was a pointer or not when reading code.
– JPhi1618
Apr 24 at 19:53
3
Just to note: Wirth's language Oberon does almost exactly this, allowing the Pascal-like^
to be omitted in the expressionsp^.x
andp^[i]
, and using types to disambiguate.
– Mike Spivey
Apr 24 at 20:51
2
Basically the same question on SO: stackoverflow.com/questions/13366083/…
– DaveInCaz
Apr 25 at 18:25
11
11
You can still write
(*structure).member
, if you like that more. (I don't, very probably K&R didn't, either. It is a bit awkward to handle because of C operator precedence, and that might answer your question)– tofro
Apr 24 at 16:14
You can still write
(*structure).member
, if you like that more. (I don't, very probably K&R didn't, either. It is a bit awkward to handle because of C operator precedence, and that might answer your question)– tofro
Apr 24 at 16:14
21
21
Just out of curiosity: In your hypothetical language where
a.b
could be interpreted as (*a).b
if a is a pointer-to-struct, would it also be automatically be interpreted as (**a).b
if a is a pointer-to-pointer-to-struct? Just to point out a possible consequence…– wrtlprnft
Apr 24 at 19:29
Just out of curiosity: In your hypothetical language where
a.b
could be interpreted as (*a).b
if a is a pointer-to-struct, would it also be automatically be interpreted as (**a).b
if a is a pointer-to-pointer-to-struct? Just to point out a possible consequence…– wrtlprnft
Apr 24 at 19:29
30
30
People complain a lot about pointers being confusing. Imagine adding to that confusion by not knowing if a variable was a pointer or not when reading code.
– JPhi1618
Apr 24 at 19:53
People complain a lot about pointers being confusing. Imagine adding to that confusion by not knowing if a variable was a pointer or not when reading code.
– JPhi1618
Apr 24 at 19:53
3
3
Just to note: Wirth's language Oberon does almost exactly this, allowing the Pascal-like
^
to be omitted in the expressions p^.x
and p^[i]
, and using types to disambiguate.– Mike Spivey
Apr 24 at 20:51
Just to note: Wirth's language Oberon does almost exactly this, allowing the Pascal-like
^
to be omitted in the expressions p^.x
and p^[i]
, and using types to disambiguate.– Mike Spivey
Apr 24 at 20:51
2
2
Basically the same question on SO: stackoverflow.com/questions/13366083/…
– DaveInCaz
Apr 25 at 18:25
Basically the same question on SO: stackoverflow.com/questions/13366083/…
– DaveInCaz
Apr 25 at 18:25
|
show 10 more comments
6 Answers
6
active
oldest
votes
Some of the first C code I saw was like this: 0x8040->output = 'A';
— its purpose was accessing memory mapped I/O locations. Needless to say it took me a while to figure out what this code was supposed to do, and the hex constant there really threw me.
The original K&R C placed all field names (here output
) into the same namespace. It was an error to have two fields of the same name in different structs at different offsets — but ok to have the same name at the same offset, the idea here being that two different structs could share the same initial fields, giving cheap way of doing "subclassing" to put varying data members at the end of the struct.
A struct could also be anonymous, e.g. no tag name for the struct. None the less, the members could still be used in .
or ->
expressions.
The C Programming Language (K&R C) Appendix A, p197,209
[8.5] ... Two structures may share a common initial sequence of members; that is, the same member may appear in two different structures if it has the same type in both and if all previous members are the same in both. (Actually, the compiler checks only that a name in two different structures has the same type and offset in both, but if the preceding members differ the construction is nonportable.)
...
[14.1] ... §7.1 says that in a direct or indirect structure reference (with . or ->) the name on the right must be a member of the structure named or pointed to by the expression on the left. To allow an escape from the typing rules, this restriction is not firmly enforced by the compiler. In fact, any lvalue is allowed before ., and the lvalue is then assumed to have the form of the structure of which the name on the right is a member. Also, the expression before a -> is required only to be a pointer or an integer. If an integer, it is taken to be the absolute address, in machine storage units, of the appropriate structure.
Since the K&R language and compiler didn't care what the type of the left hand side of .
and ->
was, the only way it had to tell the difference was by having the two operators.
The ANSI C line of standards simply followed suit in syntax, even as these old rules were abandoned.
7
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
add a comment |
In the embryonic form of C described in the 1974 C Reference Manual, there was no requirement that the left operand of .
actually be a structure, nor that the left operand of ->
actually be a pointer. The ->
operator meant "interpret the value of the left operand as a pointer, add the offset associated with the indicated structure member name, and dereference the resulting pointer as an object of the appropriate type. The .
operator effectively took the address of the left operand and then applied ->
.
Thus, given:
struct q int x, y; ;
int a[2];
the expressions a[0].y
and a[0]->y
would be interpreted in a fashion equivalent to ((struct q*)&a[0])->y
and ((struct q*)a[0])->y
, respectively.
If the compiler had examined the type of the left operand to the .
operator, it could have used that to select between the two behaviors for it. It was probably easier, however, to have two operators whose behaviors didn't depend upon the left operand's type.
11
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
3
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the+
operator, so the cost of using the already existing information to disambiguate.
and->
would have been minimal if there was no need to use them with other types as well.
– supercat
Apr 25 at 1:15
1
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
2
@IllusiveBrian exactly. See e.g. the names of fields instruct tm
(described e.g. in ctime(3)): they are liketm_sec
,tm_min
,tm_hour
etc.. Similarlystruct stat
(see stat(2)), which hasst_dev
,st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility
– Ruslan
Apr 25 at 14:55
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far asp=thing
will need to know whether type ofthing
is a pointer or array, and the target/element type, if the next token is a+
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a.
or->
instead.
– supercat
Apr 25 at 15:10
|
show 1 more comment
Despite your assertion, there would in fact be situations where it would be ambigious.
First off, early C compilers were very simple. This was in fact the main appeal of the language, as compilers for it were very easy to create and could run on very small systems, like early 16/32 microprocessors.
Adding a bunch of code for hitting all the niche cases of type inference would have drastically added to the amount of code required to make a C compiler. In fact, I've argued as a (half) joke that K&R C had type inference, but it always inferred int
. If you didn't tell C what type an object was, it assumed int
(which could cause some really gnarly bugs, let me tell you...)
Secondly, since K&R C was weakly (barely) typed, the information in many cases flat out wasn't available. The destination type of a pointer assignment can be an int, or vice versa, and K&R C has no problem with that. The compiler simply cannot infer a dereference. The coder is assumed to know what she's doing.
Also realize that in C pointers and arrays are essentially syntactic sugar for each other. This means now your .
operator would have to automagically work on arrays too. For instance, if member
happened to be a char array, now structure.member would return with the first character. And again, both chars and pointer are assignable into ints, so context doesn't help you.
This being said, you aren't the first to notice this issue. In fact, Ada was designed that dereferencing a pointer object is always assumed when a dot is used. In those cases where you want the actual pointer, you have to use .all
. The ambiguity (pointer vs. pointed to object) is still there, but resolved by moving the extra syntax to the weirder case.
11
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
8
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
add a comment |
I think there are two factors that led to standardization of the distinct operator "->" for accessing data members using a pointer.
- You assume that the C compiler would recognize the type of the LHS as being a pointer. But programmers could, and often did, override the initial typing (variable declaration) by using a typecast.
- In order to make the code more readable and less prone to unintended side-effects, it is useful to distinguish operations using pointers.
A very common feature of idiomatic C code is that a structure passed to a function as a pointer is modified within the function. Thus, the result is returned implicitly, by the side-effect of the structure variable in the calling function having been modified by the callee. This sort of approach violates modern sensibilities about loosely coupled code, but it was a simple and efficient means of dealing with complexity in C code. I would say the programmer was greatly assisted in maintaining the readability of such code by having distinct operations that made it clear whether some (possibly shared) memory pointer was the thing whose target was being modified.
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
1
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
I think this is the best answer, C (and thus C++)'s use of->
is a feature not a bug.
– Jack Aidley
Apr 25 at 13:46
add a comment |
I dislike answering my own questions. To that end, I have accepted the answer that is supported by the most historical sources. (Retrocomputing is about actual history, after all.) However, I'm not yet satisfied that the other answers completely answer the question.
Dennis Ritchie, the creator of the C language and co-creator of Unix, presented a history of C at an ACM conference in April 1993. The conference proceeding is available online as The Development of the C Language, and is quoted below.
C evolved from the short-lived B language, which in turn derived from BCPL. These two prior languages were typeless. All variables were word-sized values, which could represent signed integers, unsigned integers, characters, or pointers. Pointers could be used to reference the elements of arrays, strings, or data structures. The type that a word represented depended on the operators being used on it. As Ritchie noted,
Both languages are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern. Memory in these languages consists of a linear array of such cells, and the meaning of the contents of a cell depends on the operation applied. The
+
operator, for example, simply adds its operands using the machine's integer add instruction, and the other arithmetic operations are equally unconscious of the actual meaning of their operands.
Ritchie's paper goes in depth about how the syntax for arrays developed. In BCPL, memory was accessed with the syntax pointer!offset
. B changed memory access to a unary prefix operator *
. Arrays could then be accessed with the expression *(array+offset)
. To "sweeten such array accesses", Ritchie added the now-familiar syntax array[offset]
to B.
Structures didn't exist in BCPL or B; they were introduced in C. Ritchie doesn't discuss the history of structures as much as he does arrays. However, it does seem that early versions of C continued the practice that the meaning of a value was determined by the operators that used it. Ritchie notes:
Beguiled by the example of PL/I, early C did not tie structure pointers firmly to the structures they pointed to, and permitted programmers to write pointer->member almost without regard to the type of pointer; such an expression was taken uncritically as a reference to a region of memory designated by the pointer, while the member name specified only an offset and a type.
and
Compilers in 1977, and even well after, did not complain about usages such as assigning between integers and pointers or using objects of the wrong type to refer to structure members.
He doesn't explicitly explain the origins of .
and ->
, but it is reasonable to conclude that their purpose was to simplify expressions (much like []
did for arrays). As many have noted, pointer->member
is a shorter way to write *(pointer+member)
. Similarly, variable.member
is a shorter way to write *(&variable + member)
.
The critical thing here is that these two operations are not the same. Considering that embryonic C continued the tradition of leaving the determination of type to operators, two different operators were therefore needed. Embryonic C was not sophisticated enough to "know" the type of operands.
As C matured, later compilers would have enough information to make the distinction. However, Ritchie notes the importance of backward-compatibility for existing code, even as the language was evolving:
As should be clear from the history above, C evolved from typeless languages. It did not suddenly appear to its earliest users and developers as an entirely new language with its own rules; instead we continually had to adapt existing programs as the language developed, and make allowance for an existing body of code.
add a comment |
Just an aside: If the operator '*' were suffix not prefix, it would be neater; it would be yet neater if the suffixed reference-following operator were actually '.', as, I believe, it was in a language whereon C was based. In C p->f is sugar for (*p).f, but with suffix operator the sugar would be useless: p*.f or p..f . Years ago this was noticed, by whom I know not. I feel that one would be less tempted to wish '->' away.
New contributor
2
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
add a comment |
Your Answer
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "648"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
noCode: true, onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fretrocomputing.stackexchange.com%2fquestions%2f10812%2fwhy-did-c-use-the-operator-instead-of-reusing-the-operator%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
6 Answers
6
active
oldest
votes
6 Answers
6
active
oldest
votes
active
oldest
votes
active
oldest
votes
Some of the first C code I saw was like this: 0x8040->output = 'A';
— its purpose was accessing memory mapped I/O locations. Needless to say it took me a while to figure out what this code was supposed to do, and the hex constant there really threw me.
The original K&R C placed all field names (here output
) into the same namespace. It was an error to have two fields of the same name in different structs at different offsets — but ok to have the same name at the same offset, the idea here being that two different structs could share the same initial fields, giving cheap way of doing "subclassing" to put varying data members at the end of the struct.
A struct could also be anonymous, e.g. no tag name for the struct. None the less, the members could still be used in .
or ->
expressions.
The C Programming Language (K&R C) Appendix A, p197,209
[8.5] ... Two structures may share a common initial sequence of members; that is, the same member may appear in two different structures if it has the same type in both and if all previous members are the same in both. (Actually, the compiler checks only that a name in two different structures has the same type and offset in both, but if the preceding members differ the construction is nonportable.)
...
[14.1] ... §7.1 says that in a direct or indirect structure reference (with . or ->) the name on the right must be a member of the structure named or pointed to by the expression on the left. To allow an escape from the typing rules, this restriction is not firmly enforced by the compiler. In fact, any lvalue is allowed before ., and the lvalue is then assumed to have the form of the structure of which the name on the right is a member. Also, the expression before a -> is required only to be a pointer or an integer. If an integer, it is taken to be the absolute address, in machine storage units, of the appropriate structure.
Since the K&R language and compiler didn't care what the type of the left hand side of .
and ->
was, the only way it had to tell the difference was by having the two operators.
The ANSI C line of standards simply followed suit in syntax, even as these old rules were abandoned.
7
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
add a comment |
Some of the first C code I saw was like this: 0x8040->output = 'A';
— its purpose was accessing memory mapped I/O locations. Needless to say it took me a while to figure out what this code was supposed to do, and the hex constant there really threw me.
The original K&R C placed all field names (here output
) into the same namespace. It was an error to have two fields of the same name in different structs at different offsets — but ok to have the same name at the same offset, the idea here being that two different structs could share the same initial fields, giving cheap way of doing "subclassing" to put varying data members at the end of the struct.
A struct could also be anonymous, e.g. no tag name for the struct. None the less, the members could still be used in .
or ->
expressions.
The C Programming Language (K&R C) Appendix A, p197,209
[8.5] ... Two structures may share a common initial sequence of members; that is, the same member may appear in two different structures if it has the same type in both and if all previous members are the same in both. (Actually, the compiler checks only that a name in two different structures has the same type and offset in both, but if the preceding members differ the construction is nonportable.)
...
[14.1] ... §7.1 says that in a direct or indirect structure reference (with . or ->) the name on the right must be a member of the structure named or pointed to by the expression on the left. To allow an escape from the typing rules, this restriction is not firmly enforced by the compiler. In fact, any lvalue is allowed before ., and the lvalue is then assumed to have the form of the structure of which the name on the right is a member. Also, the expression before a -> is required only to be a pointer or an integer. If an integer, it is taken to be the absolute address, in machine storage units, of the appropriate structure.
Since the K&R language and compiler didn't care what the type of the left hand side of .
and ->
was, the only way it had to tell the difference was by having the two operators.
The ANSI C line of standards simply followed suit in syntax, even as these old rules were abandoned.
7
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
add a comment |
Some of the first C code I saw was like this: 0x8040->output = 'A';
— its purpose was accessing memory mapped I/O locations. Needless to say it took me a while to figure out what this code was supposed to do, and the hex constant there really threw me.
The original K&R C placed all field names (here output
) into the same namespace. It was an error to have two fields of the same name in different structs at different offsets — but ok to have the same name at the same offset, the idea here being that two different structs could share the same initial fields, giving cheap way of doing "subclassing" to put varying data members at the end of the struct.
A struct could also be anonymous, e.g. no tag name for the struct. None the less, the members could still be used in .
or ->
expressions.
The C Programming Language (K&R C) Appendix A, p197,209
[8.5] ... Two structures may share a common initial sequence of members; that is, the same member may appear in two different structures if it has the same type in both and if all previous members are the same in both. (Actually, the compiler checks only that a name in two different structures has the same type and offset in both, but if the preceding members differ the construction is nonportable.)
...
[14.1] ... §7.1 says that in a direct or indirect structure reference (with . or ->) the name on the right must be a member of the structure named or pointed to by the expression on the left. To allow an escape from the typing rules, this restriction is not firmly enforced by the compiler. In fact, any lvalue is allowed before ., and the lvalue is then assumed to have the form of the structure of which the name on the right is a member. Also, the expression before a -> is required only to be a pointer or an integer. If an integer, it is taken to be the absolute address, in machine storage units, of the appropriate structure.
Since the K&R language and compiler didn't care what the type of the left hand side of .
and ->
was, the only way it had to tell the difference was by having the two operators.
The ANSI C line of standards simply followed suit in syntax, even as these old rules were abandoned.
Some of the first C code I saw was like this: 0x8040->output = 'A';
— its purpose was accessing memory mapped I/O locations. Needless to say it took me a while to figure out what this code was supposed to do, and the hex constant there really threw me.
The original K&R C placed all field names (here output
) into the same namespace. It was an error to have two fields of the same name in different structs at different offsets — but ok to have the same name at the same offset, the idea here being that two different structs could share the same initial fields, giving cheap way of doing "subclassing" to put varying data members at the end of the struct.
A struct could also be anonymous, e.g. no tag name for the struct. None the less, the members could still be used in .
or ->
expressions.
The C Programming Language (K&R C) Appendix A, p197,209
[8.5] ... Two structures may share a common initial sequence of members; that is, the same member may appear in two different structures if it has the same type in both and if all previous members are the same in both. (Actually, the compiler checks only that a name in two different structures has the same type and offset in both, but if the preceding members differ the construction is nonportable.)
...
[14.1] ... §7.1 says that in a direct or indirect structure reference (with . or ->) the name on the right must be a member of the structure named or pointed to by the expression on the left. To allow an escape from the typing rules, this restriction is not firmly enforced by the compiler. In fact, any lvalue is allowed before ., and the lvalue is then assumed to have the form of the structure of which the name on the right is a member. Also, the expression before a -> is required only to be a pointer or an integer. If an integer, it is taken to be the absolute address, in machine storage units, of the appropriate structure.
Since the K&R language and compiler didn't care what the type of the left hand side of .
and ->
was, the only way it had to tell the difference was by having the two operators.
The ANSI C line of standards simply followed suit in syntax, even as these old rules were abandoned.
edited yesterday
cleberz
1032
1032
answered Apr 25 at 0:09
Erik EidtErik Eidt
1,522613
1,522613
7
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
add a comment |
7
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
7
7
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
Aha, so that’s why POSIX struct member names are all prefixed (e. g. stat::st_uid). I thought it was to enable shenanigans like #define st_atime st_atim.tv_sec.
– Roman Odaisky
Apr 25 at 12:56
add a comment |
In the embryonic form of C described in the 1974 C Reference Manual, there was no requirement that the left operand of .
actually be a structure, nor that the left operand of ->
actually be a pointer. The ->
operator meant "interpret the value of the left operand as a pointer, add the offset associated with the indicated structure member name, and dereference the resulting pointer as an object of the appropriate type. The .
operator effectively took the address of the left operand and then applied ->
.
Thus, given:
struct q int x, y; ;
int a[2];
the expressions a[0].y
and a[0]->y
would be interpreted in a fashion equivalent to ((struct q*)&a[0])->y
and ((struct q*)a[0])->y
, respectively.
If the compiler had examined the type of the left operand to the .
operator, it could have used that to select between the two behaviors for it. It was probably easier, however, to have two operators whose behaviors didn't depend upon the left operand's type.
11
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
3
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the+
operator, so the cost of using the already existing information to disambiguate.
and->
would have been minimal if there was no need to use them with other types as well.
– supercat
Apr 25 at 1:15
1
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
2
@IllusiveBrian exactly. See e.g. the names of fields instruct tm
(described e.g. in ctime(3)): they are liketm_sec
,tm_min
,tm_hour
etc.. Similarlystruct stat
(see stat(2)), which hasst_dev
,st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility
– Ruslan
Apr 25 at 14:55
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far asp=thing
will need to know whether type ofthing
is a pointer or array, and the target/element type, if the next token is a+
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a.
or->
instead.
– supercat
Apr 25 at 15:10
|
show 1 more comment
In the embryonic form of C described in the 1974 C Reference Manual, there was no requirement that the left operand of .
actually be a structure, nor that the left operand of ->
actually be a pointer. The ->
operator meant "interpret the value of the left operand as a pointer, add the offset associated with the indicated structure member name, and dereference the resulting pointer as an object of the appropriate type. The .
operator effectively took the address of the left operand and then applied ->
.
Thus, given:
struct q int x, y; ;
int a[2];
the expressions a[0].y
and a[0]->y
would be interpreted in a fashion equivalent to ((struct q*)&a[0])->y
and ((struct q*)a[0])->y
, respectively.
If the compiler had examined the type of the left operand to the .
operator, it could have used that to select between the two behaviors for it. It was probably easier, however, to have two operators whose behaviors didn't depend upon the left operand's type.
11
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
3
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the+
operator, so the cost of using the already existing information to disambiguate.
and->
would have been minimal if there was no need to use them with other types as well.
– supercat
Apr 25 at 1:15
1
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
2
@IllusiveBrian exactly. See e.g. the names of fields instruct tm
(described e.g. in ctime(3)): they are liketm_sec
,tm_min
,tm_hour
etc.. Similarlystruct stat
(see stat(2)), which hasst_dev
,st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility
– Ruslan
Apr 25 at 14:55
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far asp=thing
will need to know whether type ofthing
is a pointer or array, and the target/element type, if the next token is a+
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a.
or->
instead.
– supercat
Apr 25 at 15:10
|
show 1 more comment
In the embryonic form of C described in the 1974 C Reference Manual, there was no requirement that the left operand of .
actually be a structure, nor that the left operand of ->
actually be a pointer. The ->
operator meant "interpret the value of the left operand as a pointer, add the offset associated with the indicated structure member name, and dereference the resulting pointer as an object of the appropriate type. The .
operator effectively took the address of the left operand and then applied ->
.
Thus, given:
struct q int x, y; ;
int a[2];
the expressions a[0].y
and a[0]->y
would be interpreted in a fashion equivalent to ((struct q*)&a[0])->y
and ((struct q*)a[0])->y
, respectively.
If the compiler had examined the type of the left operand to the .
operator, it could have used that to select between the two behaviors for it. It was probably easier, however, to have two operators whose behaviors didn't depend upon the left operand's type.
In the embryonic form of C described in the 1974 C Reference Manual, there was no requirement that the left operand of .
actually be a structure, nor that the left operand of ->
actually be a pointer. The ->
operator meant "interpret the value of the left operand as a pointer, add the offset associated with the indicated structure member name, and dereference the resulting pointer as an object of the appropriate type. The .
operator effectively took the address of the left operand and then applied ->
.
Thus, given:
struct q int x, y; ;
int a[2];
the expressions a[0].y
and a[0]->y
would be interpreted in a fashion equivalent to ((struct q*)&a[0])->y
and ((struct q*)a[0])->y
, respectively.
If the compiler had examined the type of the left operand to the .
operator, it could have used that to select between the two behaviors for it. It was probably easier, however, to have two operators whose behaviors didn't depend upon the left operand's type.
answered Apr 24 at 18:42
supercatsupercat
8,7351044
8,7351044
11
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
3
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the+
operator, so the cost of using the already existing information to disambiguate.
and->
would have been minimal if there was no need to use them with other types as well.
– supercat
Apr 25 at 1:15
1
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
2
@IllusiveBrian exactly. See e.g. the names of fields instruct tm
(described e.g. in ctime(3)): they are liketm_sec
,tm_min
,tm_hour
etc.. Similarlystruct stat
(see stat(2)), which hasst_dev
,st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility
– Ruslan
Apr 25 at 14:55
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far asp=thing
will need to know whether type ofthing
is a pointer or array, and the target/element type, if the next token is a+
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a.
or->
instead.
– supercat
Apr 25 at 15:10
|
show 1 more comment
11
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
3
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the+
operator, so the cost of using the already existing information to disambiguate.
and->
would have been minimal if there was no need to use them with other types as well.
– supercat
Apr 25 at 1:15
1
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
2
@IllusiveBrian exactly. See e.g. the names of fields instruct tm
(described e.g. in ctime(3)): they are liketm_sec
,tm_min
,tm_hour
etc.. Similarlystruct stat
(see stat(2)), which hasst_dev
,st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility
– Ruslan
Apr 25 at 14:55
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far asp=thing
will need to know whether type ofthing
is a pointer or array, and the target/element type, if the next token is a+
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a.
or->
instead.
– supercat
Apr 25 at 15:10
11
11
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
To the point. As well as the last part about being 'easier'. C wasn'T designed to be a language as comfortable as possible, but to be translated as linear as possible. Resolving contextual information adds complexity and ambiguity. Nothing one wants t have when the task is to write an OS as close to the machine as possible while having the luxury of structured programming support.
– Raffzahn
Apr 24 at 18:55
3
3
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the
+
operator, so the cost of using the already existing information to disambiguate .
and ->
would have been minimal if there was no need to use them with other types as well.– supercat
Apr 25 at 1:15
@Raffzahn: Even in 1974 C, the contextual information had be kept to process the
+
operator, so the cost of using the already existing information to disambiguate .
and ->
would have been minimal if there was no need to use them with other types as well.– supercat
Apr 25 at 1:15
1
1
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
Would this also mean that structure member names had to be globally unique?
– IllusiveBrian
Apr 25 at 12:45
2
2
@IllusiveBrian exactly. See e.g. the names of fields in
struct tm
(described e.g. in ctime(3)): they are like tm_sec
, tm_min
, tm_hour
etc.. Similarly struct stat
(see stat(2)), which has st_dev
, st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility– Ruslan
Apr 25 at 14:55
@IllusiveBrian exactly. See e.g. the names of fields in
struct tm
(described e.g. in ctime(3)): they are like tm_sec
, tm_min
, tm_hour
etc.. Similarly struct stat
(see stat(2)), which has st_dev
, st_ino
etc.. These are redundant in modern C, but have to be preserved for backwards compatibility– Ruslan
Apr 25 at 14:55
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far as
p=thing
will need to know whether type of thing
is a pointer or array, and the target/element type, if the next token is a +
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a .
or ->
instead.– supercat
Apr 25 at 15:10
@Raffzahn: In simple compilers, the primary cost of having a compiler use various information to select what kind of operation an operator will perform is generally making sure the compiler will have the information in the context where it is needed. A compiler that has scanned an expression as far as
p=thing
will need to know whether type of thing
is a pointer or array, and the target/element type, if the next token is a +
. Consequently, no special effort should be required to ensure that it has that information if the next token happens to be a .
or ->
instead.– supercat
Apr 25 at 15:10
|
show 1 more comment
Despite your assertion, there would in fact be situations where it would be ambigious.
First off, early C compilers were very simple. This was in fact the main appeal of the language, as compilers for it were very easy to create and could run on very small systems, like early 16/32 microprocessors.
Adding a bunch of code for hitting all the niche cases of type inference would have drastically added to the amount of code required to make a C compiler. In fact, I've argued as a (half) joke that K&R C had type inference, but it always inferred int
. If you didn't tell C what type an object was, it assumed int
(which could cause some really gnarly bugs, let me tell you...)
Secondly, since K&R C was weakly (barely) typed, the information in many cases flat out wasn't available. The destination type of a pointer assignment can be an int, or vice versa, and K&R C has no problem with that. The compiler simply cannot infer a dereference. The coder is assumed to know what she's doing.
Also realize that in C pointers and arrays are essentially syntactic sugar for each other. This means now your .
operator would have to automagically work on arrays too. For instance, if member
happened to be a char array, now structure.member would return with the first character. And again, both chars and pointer are assignable into ints, so context doesn't help you.
This being said, you aren't the first to notice this issue. In fact, Ada was designed that dereferencing a pointer object is always assumed when a dot is used. In those cases where you want the actual pointer, you have to use .all
. The ambiguity (pointer vs. pointed to object) is still there, but resolved by moving the extra syntax to the weirder case.
11
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
8
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
add a comment |
Despite your assertion, there would in fact be situations where it would be ambigious.
First off, early C compilers were very simple. This was in fact the main appeal of the language, as compilers for it were very easy to create and could run on very small systems, like early 16/32 microprocessors.
Adding a bunch of code for hitting all the niche cases of type inference would have drastically added to the amount of code required to make a C compiler. In fact, I've argued as a (half) joke that K&R C had type inference, but it always inferred int
. If you didn't tell C what type an object was, it assumed int
(which could cause some really gnarly bugs, let me tell you...)
Secondly, since K&R C was weakly (barely) typed, the information in many cases flat out wasn't available. The destination type of a pointer assignment can be an int, or vice versa, and K&R C has no problem with that. The compiler simply cannot infer a dereference. The coder is assumed to know what she's doing.
Also realize that in C pointers and arrays are essentially syntactic sugar for each other. This means now your .
operator would have to automagically work on arrays too. For instance, if member
happened to be a char array, now structure.member would return with the first character. And again, both chars and pointer are assignable into ints, so context doesn't help you.
This being said, you aren't the first to notice this issue. In fact, Ada was designed that dereferencing a pointer object is always assumed when a dot is used. In those cases where you want the actual pointer, you have to use .all
. The ambiguity (pointer vs. pointed to object) is still there, but resolved by moving the extra syntax to the weirder case.
11
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
8
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
add a comment |
Despite your assertion, there would in fact be situations where it would be ambigious.
First off, early C compilers were very simple. This was in fact the main appeal of the language, as compilers for it were very easy to create and could run on very small systems, like early 16/32 microprocessors.
Adding a bunch of code for hitting all the niche cases of type inference would have drastically added to the amount of code required to make a C compiler. In fact, I've argued as a (half) joke that K&R C had type inference, but it always inferred int
. If you didn't tell C what type an object was, it assumed int
(which could cause some really gnarly bugs, let me tell you...)
Secondly, since K&R C was weakly (barely) typed, the information in many cases flat out wasn't available. The destination type of a pointer assignment can be an int, or vice versa, and K&R C has no problem with that. The compiler simply cannot infer a dereference. The coder is assumed to know what she's doing.
Also realize that in C pointers and arrays are essentially syntactic sugar for each other. This means now your .
operator would have to automagically work on arrays too. For instance, if member
happened to be a char array, now structure.member would return with the first character. And again, both chars and pointer are assignable into ints, so context doesn't help you.
This being said, you aren't the first to notice this issue. In fact, Ada was designed that dereferencing a pointer object is always assumed when a dot is used. In those cases where you want the actual pointer, you have to use .all
. The ambiguity (pointer vs. pointed to object) is still there, but resolved by moving the extra syntax to the weirder case.
Despite your assertion, there would in fact be situations where it would be ambigious.
First off, early C compilers were very simple. This was in fact the main appeal of the language, as compilers for it were very easy to create and could run on very small systems, like early 16/32 microprocessors.
Adding a bunch of code for hitting all the niche cases of type inference would have drastically added to the amount of code required to make a C compiler. In fact, I've argued as a (half) joke that K&R C had type inference, but it always inferred int
. If you didn't tell C what type an object was, it assumed int
(which could cause some really gnarly bugs, let me tell you...)
Secondly, since K&R C was weakly (barely) typed, the information in many cases flat out wasn't available. The destination type of a pointer assignment can be an int, or vice versa, and K&R C has no problem with that. The compiler simply cannot infer a dereference. The coder is assumed to know what she's doing.
Also realize that in C pointers and arrays are essentially syntactic sugar for each other. This means now your .
operator would have to automagically work on arrays too. For instance, if member
happened to be a char array, now structure.member would return with the first character. And again, both chars and pointer are assignable into ints, so context doesn't help you.
This being said, you aren't the first to notice this issue. In fact, Ada was designed that dereferencing a pointer object is always assumed when a dot is used. In those cases where you want the actual pointer, you have to use .all
. The ambiguity (pointer vs. pointed to object) is still there, but resolved by moving the extra syntax to the weirder case.
edited 2 days ago
Community♦
1
1
answered Apr 24 at 20:25
T.E.D.T.E.D.
96937
96937
11
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
8
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
add a comment |
11
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
8
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
11
11
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
I love it. "K&R C had type inference, but it always inferred int". Brilliant.
– Peter A. Schneider
Apr 25 at 11:56
8
8
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
"The coder is assumed to know what she's doing" is the C philosophy in a nutshell. That's simultaneously C's greatest strength and greatest weakness.
– John Bode
Apr 25 at 14:00
add a comment |
I think there are two factors that led to standardization of the distinct operator "->" for accessing data members using a pointer.
- You assume that the C compiler would recognize the type of the LHS as being a pointer. But programmers could, and often did, override the initial typing (variable declaration) by using a typecast.
- In order to make the code more readable and less prone to unintended side-effects, it is useful to distinguish operations using pointers.
A very common feature of idiomatic C code is that a structure passed to a function as a pointer is modified within the function. Thus, the result is returned implicitly, by the side-effect of the structure variable in the calling function having been modified by the callee. This sort of approach violates modern sensibilities about loosely coupled code, but it was a simple and efficient means of dealing with complexity in C code. I would say the programmer was greatly assisted in maintaining the readability of such code by having distinct operations that made it clear whether some (possibly shared) memory pointer was the thing whose target was being modified.
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
1
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
I think this is the best answer, C (and thus C++)'s use of->
is a feature not a bug.
– Jack Aidley
Apr 25 at 13:46
add a comment |
I think there are two factors that led to standardization of the distinct operator "->" for accessing data members using a pointer.
- You assume that the C compiler would recognize the type of the LHS as being a pointer. But programmers could, and often did, override the initial typing (variable declaration) by using a typecast.
- In order to make the code more readable and less prone to unintended side-effects, it is useful to distinguish operations using pointers.
A very common feature of idiomatic C code is that a structure passed to a function as a pointer is modified within the function. Thus, the result is returned implicitly, by the side-effect of the structure variable in the calling function having been modified by the callee. This sort of approach violates modern sensibilities about loosely coupled code, but it was a simple and efficient means of dealing with complexity in C code. I would say the programmer was greatly assisted in maintaining the readability of such code by having distinct operations that made it clear whether some (possibly shared) memory pointer was the thing whose target was being modified.
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
1
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
I think this is the best answer, C (and thus C++)'s use of->
is a feature not a bug.
– Jack Aidley
Apr 25 at 13:46
add a comment |
I think there are two factors that led to standardization of the distinct operator "->" for accessing data members using a pointer.
- You assume that the C compiler would recognize the type of the LHS as being a pointer. But programmers could, and often did, override the initial typing (variable declaration) by using a typecast.
- In order to make the code more readable and less prone to unintended side-effects, it is useful to distinguish operations using pointers.
A very common feature of idiomatic C code is that a structure passed to a function as a pointer is modified within the function. Thus, the result is returned implicitly, by the side-effect of the structure variable in the calling function having been modified by the callee. This sort of approach violates modern sensibilities about loosely coupled code, but it was a simple and efficient means of dealing with complexity in C code. I would say the programmer was greatly assisted in maintaining the readability of such code by having distinct operations that made it clear whether some (possibly shared) memory pointer was the thing whose target was being modified.
I think there are two factors that led to standardization of the distinct operator "->" for accessing data members using a pointer.
- You assume that the C compiler would recognize the type of the LHS as being a pointer. But programmers could, and often did, override the initial typing (variable declaration) by using a typecast.
- In order to make the code more readable and less prone to unintended side-effects, it is useful to distinguish operations using pointers.
A very common feature of idiomatic C code is that a structure passed to a function as a pointer is modified within the function. Thus, the result is returned implicitly, by the side-effect of the structure variable in the calling function having been modified by the callee. This sort of approach violates modern sensibilities about loosely coupled code, but it was a simple and efficient means of dealing with complexity in C code. I would say the programmer was greatly assisted in maintaining the readability of such code by having distinct operations that made it clear whether some (possibly shared) memory pointer was the thing whose target was being modified.
edited Apr 24 at 17:04
answered Apr 24 at 15:55
Brian HBrian H
18.5k69159
18.5k69159
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
1
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
I think this is the best answer, C (and thus C++)'s use of->
is a feature not a bug.
– Jack Aidley
Apr 25 at 13:46
add a comment |
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
1
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
I think this is the best answer, C (and thus C++)'s use of->
is a feature not a bug.
– Jack Aidley
Apr 25 at 13:46
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
Could you give an example of your point #1? The type of the result of a typecast is well-defined... it's the type you are typecasting to. Should that be a pointer to a structure, the compiler has enough information to access its members. Whatever the type was prior to the typecast is irrelevant.
– Dr Sheldon
Apr 24 at 16:27
1
1
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
@DrSheldon Yes, you are correct. The compiler can check operators used against the type of a typecast too, and interpret a single operator appropriately. But then you are removing one of the compiler checks against programming errors. I think #1 and #2 actually work in concert to push the programmer toward care with pointer references. If the compiler tries to be "too smart" it ends up misinterpreting what the error-prone programmer intended, and (perhaps more important) makes the code harder to read.
– Brian H
Apr 24 at 16:57
I think this is the best answer, C (and thus C++)'s use of
->
is a feature not a bug.– Jack Aidley
Apr 25 at 13:46
I think this is the best answer, C (and thus C++)'s use of
->
is a feature not a bug.– Jack Aidley
Apr 25 at 13:46
add a comment |
I dislike answering my own questions. To that end, I have accepted the answer that is supported by the most historical sources. (Retrocomputing is about actual history, after all.) However, I'm not yet satisfied that the other answers completely answer the question.
Dennis Ritchie, the creator of the C language and co-creator of Unix, presented a history of C at an ACM conference in April 1993. The conference proceeding is available online as The Development of the C Language, and is quoted below.
C evolved from the short-lived B language, which in turn derived from BCPL. These two prior languages were typeless. All variables were word-sized values, which could represent signed integers, unsigned integers, characters, or pointers. Pointers could be used to reference the elements of arrays, strings, or data structures. The type that a word represented depended on the operators being used on it. As Ritchie noted,
Both languages are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern. Memory in these languages consists of a linear array of such cells, and the meaning of the contents of a cell depends on the operation applied. The
+
operator, for example, simply adds its operands using the machine's integer add instruction, and the other arithmetic operations are equally unconscious of the actual meaning of their operands.
Ritchie's paper goes in depth about how the syntax for arrays developed. In BCPL, memory was accessed with the syntax pointer!offset
. B changed memory access to a unary prefix operator *
. Arrays could then be accessed with the expression *(array+offset)
. To "sweeten such array accesses", Ritchie added the now-familiar syntax array[offset]
to B.
Structures didn't exist in BCPL or B; they were introduced in C. Ritchie doesn't discuss the history of structures as much as he does arrays. However, it does seem that early versions of C continued the practice that the meaning of a value was determined by the operators that used it. Ritchie notes:
Beguiled by the example of PL/I, early C did not tie structure pointers firmly to the structures they pointed to, and permitted programmers to write pointer->member almost without regard to the type of pointer; such an expression was taken uncritically as a reference to a region of memory designated by the pointer, while the member name specified only an offset and a type.
and
Compilers in 1977, and even well after, did not complain about usages such as assigning between integers and pointers or using objects of the wrong type to refer to structure members.
He doesn't explicitly explain the origins of .
and ->
, but it is reasonable to conclude that their purpose was to simplify expressions (much like []
did for arrays). As many have noted, pointer->member
is a shorter way to write *(pointer+member)
. Similarly, variable.member
is a shorter way to write *(&variable + member)
.
The critical thing here is that these two operations are not the same. Considering that embryonic C continued the tradition of leaving the determination of type to operators, two different operators were therefore needed. Embryonic C was not sophisticated enough to "know" the type of operands.
As C matured, later compilers would have enough information to make the distinction. However, Ritchie notes the importance of backward-compatibility for existing code, even as the language was evolving:
As should be clear from the history above, C evolved from typeless languages. It did not suddenly appear to its earliest users and developers as an entirely new language with its own rules; instead we continually had to adapt existing programs as the language developed, and make allowance for an existing body of code.
add a comment |
I dislike answering my own questions. To that end, I have accepted the answer that is supported by the most historical sources. (Retrocomputing is about actual history, after all.) However, I'm not yet satisfied that the other answers completely answer the question.
Dennis Ritchie, the creator of the C language and co-creator of Unix, presented a history of C at an ACM conference in April 1993. The conference proceeding is available online as The Development of the C Language, and is quoted below.
C evolved from the short-lived B language, which in turn derived from BCPL. These two prior languages were typeless. All variables were word-sized values, which could represent signed integers, unsigned integers, characters, or pointers. Pointers could be used to reference the elements of arrays, strings, or data structures. The type that a word represented depended on the operators being used on it. As Ritchie noted,
Both languages are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern. Memory in these languages consists of a linear array of such cells, and the meaning of the contents of a cell depends on the operation applied. The
+
operator, for example, simply adds its operands using the machine's integer add instruction, and the other arithmetic operations are equally unconscious of the actual meaning of their operands.
Ritchie's paper goes in depth about how the syntax for arrays developed. In BCPL, memory was accessed with the syntax pointer!offset
. B changed memory access to a unary prefix operator *
. Arrays could then be accessed with the expression *(array+offset)
. To "sweeten such array accesses", Ritchie added the now-familiar syntax array[offset]
to B.
Structures didn't exist in BCPL or B; they were introduced in C. Ritchie doesn't discuss the history of structures as much as he does arrays. However, it does seem that early versions of C continued the practice that the meaning of a value was determined by the operators that used it. Ritchie notes:
Beguiled by the example of PL/I, early C did not tie structure pointers firmly to the structures they pointed to, and permitted programmers to write pointer->member almost without regard to the type of pointer; such an expression was taken uncritically as a reference to a region of memory designated by the pointer, while the member name specified only an offset and a type.
and
Compilers in 1977, and even well after, did not complain about usages such as assigning between integers and pointers or using objects of the wrong type to refer to structure members.
He doesn't explicitly explain the origins of .
and ->
, but it is reasonable to conclude that their purpose was to simplify expressions (much like []
did for arrays). As many have noted, pointer->member
is a shorter way to write *(pointer+member)
. Similarly, variable.member
is a shorter way to write *(&variable + member)
.
The critical thing here is that these two operations are not the same. Considering that embryonic C continued the tradition of leaving the determination of type to operators, two different operators were therefore needed. Embryonic C was not sophisticated enough to "know" the type of operands.
As C matured, later compilers would have enough information to make the distinction. However, Ritchie notes the importance of backward-compatibility for existing code, even as the language was evolving:
As should be clear from the history above, C evolved from typeless languages. It did not suddenly appear to its earliest users and developers as an entirely new language with its own rules; instead we continually had to adapt existing programs as the language developed, and make allowance for an existing body of code.
add a comment |
I dislike answering my own questions. To that end, I have accepted the answer that is supported by the most historical sources. (Retrocomputing is about actual history, after all.) However, I'm not yet satisfied that the other answers completely answer the question.
Dennis Ritchie, the creator of the C language and co-creator of Unix, presented a history of C at an ACM conference in April 1993. The conference proceeding is available online as The Development of the C Language, and is quoted below.
C evolved from the short-lived B language, which in turn derived from BCPL. These two prior languages were typeless. All variables were word-sized values, which could represent signed integers, unsigned integers, characters, or pointers. Pointers could be used to reference the elements of arrays, strings, or data structures. The type that a word represented depended on the operators being used on it. As Ritchie noted,
Both languages are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern. Memory in these languages consists of a linear array of such cells, and the meaning of the contents of a cell depends on the operation applied. The
+
operator, for example, simply adds its operands using the machine's integer add instruction, and the other arithmetic operations are equally unconscious of the actual meaning of their operands.
Ritchie's paper goes in depth about how the syntax for arrays developed. In BCPL, memory was accessed with the syntax pointer!offset
. B changed memory access to a unary prefix operator *
. Arrays could then be accessed with the expression *(array+offset)
. To "sweeten such array accesses", Ritchie added the now-familiar syntax array[offset]
to B.
Structures didn't exist in BCPL or B; they were introduced in C. Ritchie doesn't discuss the history of structures as much as he does arrays. However, it does seem that early versions of C continued the practice that the meaning of a value was determined by the operators that used it. Ritchie notes:
Beguiled by the example of PL/I, early C did not tie structure pointers firmly to the structures they pointed to, and permitted programmers to write pointer->member almost without regard to the type of pointer; such an expression was taken uncritically as a reference to a region of memory designated by the pointer, while the member name specified only an offset and a type.
and
Compilers in 1977, and even well after, did not complain about usages such as assigning between integers and pointers or using objects of the wrong type to refer to structure members.
He doesn't explicitly explain the origins of .
and ->
, but it is reasonable to conclude that their purpose was to simplify expressions (much like []
did for arrays). As many have noted, pointer->member
is a shorter way to write *(pointer+member)
. Similarly, variable.member
is a shorter way to write *(&variable + member)
.
The critical thing here is that these two operations are not the same. Considering that embryonic C continued the tradition of leaving the determination of type to operators, two different operators were therefore needed. Embryonic C was not sophisticated enough to "know" the type of operands.
As C matured, later compilers would have enough information to make the distinction. However, Ritchie notes the importance of backward-compatibility for existing code, even as the language was evolving:
As should be clear from the history above, C evolved from typeless languages. It did not suddenly appear to its earliest users and developers as an entirely new language with its own rules; instead we continually had to adapt existing programs as the language developed, and make allowance for an existing body of code.
I dislike answering my own questions. To that end, I have accepted the answer that is supported by the most historical sources. (Retrocomputing is about actual history, after all.) However, I'm not yet satisfied that the other answers completely answer the question.
Dennis Ritchie, the creator of the C language and co-creator of Unix, presented a history of C at an ACM conference in April 1993. The conference proceeding is available online as The Development of the C Language, and is quoted below.
C evolved from the short-lived B language, which in turn derived from BCPL. These two prior languages were typeless. All variables were word-sized values, which could represent signed integers, unsigned integers, characters, or pointers. Pointers could be used to reference the elements of arrays, strings, or data structures. The type that a word represented depended on the operators being used on it. As Ritchie noted,
Both languages are typeless, or rather have a single data type, the 'word,' or 'cell,' a fixed-length bit pattern. Memory in these languages consists of a linear array of such cells, and the meaning of the contents of a cell depends on the operation applied. The
+
operator, for example, simply adds its operands using the machine's integer add instruction, and the other arithmetic operations are equally unconscious of the actual meaning of their operands.
Ritchie's paper goes in depth about how the syntax for arrays developed. In BCPL, memory was accessed with the syntax pointer!offset
. B changed memory access to a unary prefix operator *
. Arrays could then be accessed with the expression *(array+offset)
. To "sweeten such array accesses", Ritchie added the now-familiar syntax array[offset]
to B.
Structures didn't exist in BCPL or B; they were introduced in C. Ritchie doesn't discuss the history of structures as much as he does arrays. However, it does seem that early versions of C continued the practice that the meaning of a value was determined by the operators that used it. Ritchie notes:
Beguiled by the example of PL/I, early C did not tie structure pointers firmly to the structures they pointed to, and permitted programmers to write pointer->member almost without regard to the type of pointer; such an expression was taken uncritically as a reference to a region of memory designated by the pointer, while the member name specified only an offset and a type.
and
Compilers in 1977, and even well after, did not complain about usages such as assigning between integers and pointers or using objects of the wrong type to refer to structure members.
He doesn't explicitly explain the origins of .
and ->
, but it is reasonable to conclude that their purpose was to simplify expressions (much like []
did for arrays). As many have noted, pointer->member
is a shorter way to write *(pointer+member)
. Similarly, variable.member
is a shorter way to write *(&variable + member)
.
The critical thing here is that these two operations are not the same. Considering that embryonic C continued the tradition of leaving the determination of type to operators, two different operators were therefore needed. Embryonic C was not sophisticated enough to "know" the type of operands.
As C matured, later compilers would have enough information to make the distinction. However, Ritchie notes the importance of backward-compatibility for existing code, even as the language was evolving:
As should be clear from the history above, C evolved from typeless languages. It did not suddenly appear to its earliest users and developers as an entirely new language with its own rules; instead we continually had to adapt existing programs as the language developed, and make allowance for an existing body of code.
answered 2 days ago
Dr SheldonDr Sheldon
2,40431237
2,40431237
add a comment |
add a comment |
Just an aside: If the operator '*' were suffix not prefix, it would be neater; it would be yet neater if the suffixed reference-following operator were actually '.', as, I believe, it was in a language whereon C was based. In C p->f is sugar for (*p).f, but with suffix operator the sugar would be useless: p*.f or p..f . Years ago this was noticed, by whom I know not. I feel that one would be less tempted to wish '->' away.
New contributor
2
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
add a comment |
Just an aside: If the operator '*' were suffix not prefix, it would be neater; it would be yet neater if the suffixed reference-following operator were actually '.', as, I believe, it was in a language whereon C was based. In C p->f is sugar for (*p).f, but with suffix operator the sugar would be useless: p*.f or p..f . Years ago this was noticed, by whom I know not. I feel that one would be less tempted to wish '->' away.
New contributor
2
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
add a comment |
Just an aside: If the operator '*' were suffix not prefix, it would be neater; it would be yet neater if the suffixed reference-following operator were actually '.', as, I believe, it was in a language whereon C was based. In C p->f is sugar for (*p).f, but with suffix operator the sugar would be useless: p*.f or p..f . Years ago this was noticed, by whom I know not. I feel that one would be less tempted to wish '->' away.
New contributor
Just an aside: If the operator '*' were suffix not prefix, it would be neater; it would be yet neater if the suffixed reference-following operator were actually '.', as, I believe, it was in a language whereon C was based. In C p->f is sugar for (*p).f, but with suffix operator the sugar would be useless: p*.f or p..f . Years ago this was noticed, by whom I know not. I feel that one would be less tempted to wish '->' away.
New contributor
New contributor
answered 2 days ago
oldProgrammeroldProgrammer
1
1
New contributor
New contributor
2
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
add a comment |
2
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
2
2
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
This doesn't answer the question. Please read the tour.
– wizzwizz4♦
2 days ago
add a comment |
Thanks for contributing an answer to Retrocomputing Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fretrocomputing.stackexchange.com%2fquestions%2f10812%2fwhy-did-c-use-the-operator-instead-of-reusing-the-operator%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
11
You can still write
(*structure).member
, if you like that more. (I don't, very probably K&R didn't, either. It is a bit awkward to handle because of C operator precedence, and that might answer your question)– tofro
Apr 24 at 16:14
21
Just out of curiosity: In your hypothetical language where
a.b
could be interpreted as(*a).b
if a is a pointer-to-struct, would it also be automatically be interpreted as(**a).b
if a is a pointer-to-pointer-to-struct? Just to point out a possible consequence…– wrtlprnft
Apr 24 at 19:29
30
People complain a lot about pointers being confusing. Imagine adding to that confusion by not knowing if a variable was a pointer or not when reading code.
– JPhi1618
Apr 24 at 19:53
3
Just to note: Wirth's language Oberon does almost exactly this, allowing the Pascal-like
^
to be omitted in the expressionsp^.x
andp^[i]
, and using types to disambiguate.– Mike Spivey
Apr 24 at 20:51
2
Basically the same question on SO: stackoverflow.com/questions/13366083/…
– DaveInCaz
Apr 25 at 18:25