最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c - The `sockaddr` and `sockaddr_in` (and other similar) structs behave and are used as if they were a union. Why are they not p

programmeradmin1浏览0评论

Some of the sockets/unix networking struct objects behave and are intended to be used as if they were a union type. In other words, many functions in the sockets API take a struct sockaddr*, and it is expected that the client will call these functions with a range of struct types by casting pointers to different types.

For example, a sockaddr_in (ipv4) and sockaddr_in6 (ipv6) struct may be cast to a struct sockaddr* by taking the address of the object and performing a pointer type cast.

I am curious to know why there is no explicit union type involved here, even though these objects are intended to be used and behave as if they were a union type.

I hope the question makes sense and is clear enough? This is a somewhat abstract or difficult to describe question.

union behave somewhat polymorphically. Perhaps this comment helps to explain what I am thinking about here.

Some of the sockets/unix networking struct objects behave and are intended to be used as if they were a union type. In other words, many functions in the sockets API take a struct sockaddr*, and it is expected that the client will call these functions with a range of struct types by casting pointers to different types.

For example, a sockaddr_in (ipv4) and sockaddr_in6 (ipv6) struct may be cast to a struct sockaddr* by taking the address of the object and performing a pointer type cast.

I am curious to know why there is no explicit union type involved here, even though these objects are intended to be used and behave as if they were a union type.

I hope the question makes sense and is clear enough? This is a somewhat abstract or difficult to describe question.

union behave somewhat polymorphically. Perhaps this comment helps to explain what I am thinking about here.

Share Improve this question asked Feb 4 at 22:22 user2138149user2138149 17.4k30 gold badges146 silver badges289 bronze badges 3
  • 3 I do not know, which is why this isn't an answer, but I think it probably has something to do with the fact that a union must be defined all at once in a single place, versus how the sockaddr_* types are spread over many headers. Keep in mind also that this is a design from the 1980s, when we hadn't yet learned that a bunch of things are bad ideas, and networking looked very different from how it does now. – zwol Commented Feb 4 at 22:31
  • 4 It is en.wikipedia./wiki/Opaque_data_type You do not have to know implementation details (and better if you do not know). It is a very common programming pattern. Unions are not suitable for it – 0___________ Commented Feb 4 at 22:32
  • Thanks, these are great comments with much insight – user2138149 Commented Feb 4 at 22:40
Add a comment  | 

5 Answers 5

Reset to default 6

The sockets API is very old, dating back to the 1980s. The ANSI C standard was ratified only in 1989. Programmers using C were not concerned with academic undefined behavior issues, like that certain type punning is formally well-defined if done through a union, but not otherwise.

C unions are cumbersome to use, due to introducing extra members. I think ISO C still does not have transparent unions.

If you make a union out of all the possible structures, the resulting object will have a size large enough to contain all of them. That may be undesirable.

Only fairly recently (in terms of the long history of the API) did POSIX add the type struct sockaddr_storage which has this property of being large enough to define storage for any sockaddr type, but that's normally only used for that specific purpose.

Code not working with any type might not want the overhead of the extra storage not needed by its type.

The API designers opted for the simple "OOP" technique of just casting pointers from a "derived" class like struct sockaddr_in * to a "base" type like struct sockaddr, ensuring that any common members were put in front, so that for instance the address family could be accessed through any type to tell that the struct sockaddr is really struct sockaddr_in, due to its tag being AF_INET.

This is not unheard of C programs, not just the socket API of POSIX.

In addition to what the other answers have said, many new protocols have appeared since the early days when sockaddr was introduced. Those protocols use their own unique sockaddr_... struct types (ie, sockaddr_un for UNIX domains, sockaddr_bth for Bluetooth, etc), sometimes in separate libraries/headers, but they are still "compatible" with socket APIs that take basic sockaddr* pointers. It would be much more difficult to develop new protocols if all of the structs had to be declared in a single union in one place.

It proved to be tremendous future proofing; IPv6 migration would have been monstrous had that not been the case. As it was we only had to retrofit for the new IPv6 type in a handful of places. Code that did what it was supposed to and call gethostbyname() or getaddrinfo() required no conversion for IPv6. And this is because these structs weren't unions so these library functions could just allocate larger structs when encountering an IPv6 address and the code actually worked.

Prior to C99, the C language had unambiguously guaranteed that if two or more structures have matching types for the first N members, a function that uses a pointer of any of those types only for the purpose of inspecting some or all of the first N members may be passed a suitably-cast pointer to any object of any of those types, and will access the corresponding members of the structure passed to it, at least if the alignment of the passed object is compatible with the alignment requirement of the type used to access it. This behavior was documented going back at least to 1974.

The authors of C99 decided to change the rules to require that a complete definition of a union type containing any involved structures be visible in parts of the code that exploit the Common Initial Sequence guarantees(*), which might have resulted in code being modified to add such union type definitions if any compilers actually required them. So far as I can tell, however, the only any compiler configuration that would process code incorrectly without such union declarations would interpret it the same way even if such a declaration were present.

Note that C99 allows implementations to continue processing the language the Committee was chartered to describe; what changed is that it allows implementations that are intended exclusively for tasks that wouldn't benefit from CIS guarantees to process a dialect that lacks a feature that had for 25 years been part of full-featured C.

(*) The purpose of this was to allow a compiler given something like:

for (int i=0; i<100; i++)
{
   positions[i].x += velocities[i].dx;
   positions[i].y += velocities[i].dy;
}

to use vector operations that would read multiple velocities at once, and add them to multiple positions, without having to accommodate the possibility that the address of positions[0] might correspond with the address of velocities[1], causing that the value of velocities[1].dx during the second iteration of the loop to be different from what it had been before position[0] was written. If velocities use a different structure type from positions, compilers could use the revised rule to justify ignoring the possibility of such overlap if they were intended for use in situations where compatibility with code exploitiung CIS guarantees was less important than the potential performance benefits the rules could offer.

The text regarding complete union type definitions was most likely a result of someone observing that there should be a means of informing a compiler that code was relying upon the Common Initial Sequence being usable with certain combinations of structures, and someone (possibly that person or someone else) observing that the existing union definition syntax could serve that purpose. Programs wouldn't usually define a union type containing two or more structures if it wouldn't be performing type punning between those types, and compilers that would honor the Common Initial Sequence guarantees could simply process union type definitions as simply defining union types. This might have been a usable comrpomise if compiler writers didn't invent their own non-standard syntax for the purpose and demand that programmers use that instead of the means provided by the Standard.

This is because the number of address families is not known at compilation time... so a union should require all of them to be known at compilation. There is a generic one, struct sockaddr with only the common fields. All the other structs, each belong to a different sockets implementation.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论
ok 不同模板 switch ($forum['model']) { /*case '0': include _include(APP_PATH . 'view/htm/read.htm'); break;*/ default: include _include(theme_load('read', $fid)); break; } } break; case '10': // 主题外链 / thread external link http_location(htmlspecialchars_decode(trim($thread['description']))); break; case '11': // 单页 / single page $attachlist = array(); $imagelist = array(); $thread['filelist'] = array(); $threadlist = NULL; $thread['files'] > 0 and list($attachlist, $imagelist, $thread['filelist']) = well_attach_find_by_tid($tid); $data = data_read_cache($tid); empty($data) and message(-1, lang('data_malformation')); $tidlist = $forum['threads'] ? page_find_by_fid($fid, $page, $pagesize) : NULL; if ($tidlist) { $tidarr = arrlist_values($tidlist, 'tid'); $threadlist = well_thread_find($tidarr, $pagesize); // 按之前tidlist排序 $threadlist = array2_sort_key($threadlist, $tidlist, 'tid'); } $allowpost = forum_access_user($fid, $gid, 'allowpost'); $allowupdate = forum_access_mod($fid, $gid, 'allowupdate'); $allowdelete = forum_access_mod($fid, $gid, 'allowdelete'); $access = array('allowpost' => $allowpost, 'allowupdate' => $allowupdate, 'allowdelete' => $allowdelete); $header['title'] = $thread['subject']; $header['mobile_link'] = $thread['url']; $header['keywords'] = $thread['keyword'] ? $thread['keyword'] : $thread['subject']; $header['description'] = $thread['description'] ? $thread['description'] : $thread['brief']; $_SESSION['fid'] = $fid; if ($ajax) { empty($conf['api_on']) and message(0, lang('closed')); $apilist['header'] = $header; $apilist['extra'] = $extra; $apilist['access'] = $access; $apilist['thread'] = well_thread_safe_info($thread); $apilist['thread_data'] = $data; $apilist['forum'] = $forum; $apilist['imagelist'] = $imagelist; $apilist['filelist'] = $thread['filelist']; $apilist['threadlist'] = $threadlist; message(0, $apilist); } else { include _include(theme_load('single_page', $fid)); } break; default: message(-1, lang('data_malformation')); break; } ?>