473,786 Members | 2,426 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Address syntax

Hi all,

In the code snippet below I successfully determine the address of val1:*

struct o val1=l_SYM_2B(& a).o[0];
print_aesthetic (&val1);

The structure o is heavyweight. I understand (hopefully correctly) that
(barring compiler optimisations) C will shallow copy the structure into
val1.

As I merely wished to pass the structure to print_aesthetic I combined the
statements to avoid creating an intermediate copy of the structure. This
was my attempt:

print_aesthetic (&(l_SYM_2B(&a) .o[0]));

I discovered that the only way to get GCC 3.4 to accept this syntax was to
append the -std=c99 compiler option. Otherwise GCC produced the error
message "invalid operands to binary +", i.e. it appeared to be trying to
interpret the address operator as a binary AND.

Is this a bug in GCC's pre-C99 support or has the syntax of C been changed
in C99 to support the code I wrote above? If so, thank goodness!

Regards,
Adam

* Footnote:
a is a local variable of type struct o.
l_SYM_2B(struct o * arg0) returns struct v1, defined as { struct o o[1]; };
print_aesthetic has the prototype struct v1 print_aesthetic (struct o *);
Nov 14 '05
27 2251
Adam Warner wrote:
Hi Andrey Tarasevich,
On Wed, 29 Dec 2004 16:37:05 -0800, Andrey Tarasevich wrote:
On the second thought, the code should probably circumvent the "lvalue
check" since it actually accesses an element of an array aggregated by
the returned struct. Array element access in C decays to pointer
arithmetics, which will formally turn the '&'s argument into an lvalue
(i.e. 'l_SYM_2B(&a).o[0]' is '*(l_SYM_2B(&a) .o + 0)' and the result of
'*' is always an lvalue).
I'm glad we agree that this part is legal C99.


Well, I'm sorry to say that, but it appears that my above statement is
incorrect in C89/C90 and irrelevant in C99. The reason why it is
incorrect in C89/C90 is explained in my other message (in short,
'l_SYM_2B(&a).o ' is an rvalue of array type and '[]' cannot be applied
to it). The reason why it is irrelevant in C99 is explained in your
other message ('&' and '[]' cancel each other out).

However, this doesn't change the fact that we agree that this part is
well-formed in C99 :)

I would still like to know whether the following code

struct S { int i[5]; };

struct S foo() { struct S s = { 0 }; return s; }

int main() { int i = foo().i[1]; }

is valid C89/C90? Comeau thinks it is not (and the standard, as I
understand it, seems to support this position). GCC, on the other hand,
accepts it. Can anyone comment on it?
However, the second problem - UB - is still there.


6.5.2.2/5: "If an attempt is made to modify the result of a function call
or to access it after the next sequence point, the behavior is undefined."

Therefore I have to copy the return value into a local variable so I can
pass it by reference to a function, thereby defeating the benefits of pass
by reference (which were to avoid copying heavyweight structures and
permit mutation of all objects via function calls).


It looks like you have to do that, yes.

Another approach would be to pass a pointer to the destination structure
to 'l_SYM_2B' function as a separate parameter and fill it in inside
'l_SYM_2B' instead of returning it from 'l_SYM_2B' by value. In other
words, you can (if you can) change the signature of 'l_SYM_2B' to
something like

void l_SYM_2B(<whate ver>* lp_a, struct v1* lp_v1)
{
/* fill in '*lp_v1' accordingly */
}

Your original code in this case would look as follows

struct v1 val1;
l_SYM_2B(&a, &val1);
print_aesthetic (&val1.o[0]);

No heavy copying is performed.
My mistake is that I can't rely upon a return value remaining on the stack
until the _calling_ function exits (and when you think about it the stack
could blow up horribly otherwise!)
That's an interesting question. In C++ you wouldn't have to worry about
the lifetime of temporary objects (assuming that you switch from using
pointers to using references) because the lifetime of temporary objects
extends at least to the end of the expression. I don't see any reason
why it should be different in C (except for the fact that C doesn't have
references).
<http://madchat.org/osdevl/stack.html>

As you can see, the data still is on the stack, but once the pop
operation is completed, we consider that part of the data invalid.
Thus, the next push operation overwrites this data. But that's OK,
because we assume that after a pop operation, the data that's popped
off is considered garbage.

If you've ever made the error of returning a pointer to a local
variable or to a parameter that was passed by value and wondered why
the value stayed valid initially, but later on got corrupted, you
should now know the reason.

The data still stays on the garbage part of the stack until the next
push operation overwrites it (that's when the data gets corrupted).


I don't think this is relevant in your case. The text you quoted talks
about dangers of returning _pointers_ to local data from functions. You
are not returning any pointers to local data from functions in your
code. Your code return the data _by_ _value_. This is a significantly
different situation.

--
Best regards,
Andrey Tarasevich
Nov 14 '05 #11
Adam Warner wrote:
I've been considering similar issues after I tried to compile this to show the addresses were the same:

printf ("&(l_SYM_2B(&a )) is %p\n", &(l_SYM_2B(&a)) );
printf ("&(l_SYM_2B(&a ).o[0]) is %p\n", &(l_SYM_2B(&a). o[0]));
A minor point: %p expects a (void *), you are causing UB
by passing a different pointer type to it. You need to cast
to (void *) in case you are on a platform where (void *) is
bigger than pointer-to-struct.

The first statement correctly generates the error: "invalid lvalue
in unary `&'" according to 6.5.3.2 (a meta question is *why* it
should be prohibited to obtain the address of a return value on
the stack. Sometimes C feels frustratingly high level :-)


What stack? (Some implementations don't have one). The C90
logic seems clear: the return value might be in a register, so
how can you take the address of it? (or of parts of it).
Reading Andrey Tarasevich's replies, it seems that C99 fixes
this problem by forcing the return value to be an lvalue if
you use operator* (or equivalently, operator[]) on it.
My question then: suppose we have in C99

struct t1 t = l_SYM_2B(&x).o[0];

does that mean that there must be an lvalue object created to be
the return-value (so that [0] can be applied to its .o),
and then that object is copied to t ? ie. there is a wasted
copy?

Nov 14 '05 #12
Hi Andrey Tarasevich,
On Wed, 29 Dec 2004 18:31:17 -0800, Andrey Tarasevich wrote:
However, this doesn't change the fact that we agree that this part is
well-formed in C99 :)

I would still like to know whether the following code

struct S { int i[5]; };

struct S foo() { struct S s = { 0 }; return s; }

int main() { int i = foo().i[1]; }

is valid C89/C90? Comeau thinks it is not (and the standard, as I
understand it, seems to support this position). GCC, on the other hand,
accepts it. Can anyone comment on it?
However, the second problem - UB - is still there.


6.5.2.2/5: "If an attempt is made to modify the result of a function call
or to access it after the next sequence point, the behavior is undefined."

Therefore I have to copy the return value into a local variable so I can
pass it by reference to a function, thereby defeating the benefits of pass
by reference (which were to avoid copying heavyweight structures and
permit mutation of all objects via function calls).


It looks like you have to do that, yes.

Another approach would be to pass a pointer to the destination structure
to 'l_SYM_2B' function as a separate parameter and fill it in inside
'l_SYM_2B' instead of returning it from 'l_SYM_2B' by value. In other
words, you can (if you can) change the signature of 'l_SYM_2B' to
something like

void l_SYM_2B(<whate ver>* lp_a, struct v1* lp_v1)
{
/* fill in '*lp_v1' accordingly */
}

Your original code in this case would look as follows

struct v1 val1;
l_SYM_2B(&a, &val1);
print_aesthetic (&val1.o[0]);

No heavy copying is performed.


Ingenious. Thanks for the tip. In other words I make it the responsibility
of the caller to supply the data structure to fill in. The caller can
therefore create the value or reference semantics it desires. For example
I could supply a reference to a new object to an increment function for
functional semantics. Or I could supply a reference to the old object and
it would be mutated instead.
My mistake is that I can't rely upon a return value remaining on the stack
until the _calling_ function exits (and when you think about it the stack
could blow up horribly otherwise!)


That's an interesting question. In C++ you wouldn't have to worry about
the lifetime of temporary objects (assuming that you switch from using
pointers to using references) because the lifetime of temporary objects
extends at least to the end of the expression.


That would be useful. It wouldn't cause the stack to blow up like I was
imagining (repeatedly calling a function within a loop) as the references
to the return values would become invalid at the end of each expression.
But it would permit me to nest references to return values.

Regards,
Adam
Nov 14 '05 #13
On Thu, 30 Dec 2004 13:58:27 +1300, Adam Warner wrote:
The second one may however be legitimate in C99. The semantics state (note
that this the final draft): <http://dev.unicals.com/c99-draft.html#6.5. 3.2>

Similarly, if the operand is the result of a [] operator, neither the &
operator nor the unary * that is implied by the [] is evaluated and the
result is as if the & operator were removed and the [] operator were
changed to a + operator. Otherwise, the result is a pointer to the
object or function designated by its operand.


I've come across "Rationale for International Standard--Programming
Languages--C" which explains some of the decisions of C99:
<http://careferencemanu al.com/>
<http://wwwold.dkuug.dk/JTC1/SC22/WG14/www/docs/n897.pdf>

In relation to 6.5.3.2 it states:

6.5.3.2 Address and indirection operators

Some implementations have not allowed the & operator to be applied to
an array or a function. (The construct was permitted in early versions
of C, then later made optional.) The C89 Committee endorsed the
construct since it is unambiguous, and since data abstraction is
enhanced by allowing the important & operator to apply uniformly to any
addressable entity.

Regards,
Adam
Nov 14 '05 #14
Andrey Tarasevich wrote:
Another approach would be to pass a pointer to the destination structure
to 'l_SYM_2B' function as a separate parameter and fill it in inside
'l_SYM_2B' instead of returning it from 'l_SYM_2B' by value. In other
words, you can (if you can) change the signature of 'l_SYM_2B' to
something like

void l_SYM_2B(<whate ver>* lp_a, struct v1* lp_v1)
{
/* fill in '*lp_v1' accordingly */
}

Your original code in this case would look as follows

struct v1 ;
l_SYM_2B(&a, &val1);
print_aesthetic (&val1.o[0]);

No heavy copying is performed.


Still l_SYM_2B has to copy an entire struct v1 into *lp_v1. That is
heavy copying (and, moreover, leaving compilers less opportunities to
optimize it away).

Regards,
Dietmar Schindler
Nov 14 '05 #15
Dietmar Schindler wrote:
Andrey Tarasevich wrote:
Another approach would be to pass a pointer to the destination structure
to 'l_SYM_2B' function as a separate parameter and fill it in inside
'l_SYM_2B' instead of returning it from 'l_SYM_2B' by value. In other
words, you can (if you can) change the signature of 'l_SYM_2B' to
something like

void l_SYM_2B(<whate ver>* lp_a, struct v1* lp_v1)
{
/* fill in '*lp_v1' accordingly */
}

Your original code in this case would look as follows

struct v1 ;
l_SYM_2B(&a, &val1);
print_aesthetic (&val1.o[0]);

No heavy copying is performed.


Still l_SYM_2B has to copy an entire struct v1 into *lp_v1. That is
heavy copying (and, moreover, leaving compilers less opportunities to
optimize it away).
...


As I understood it, the OP is not actually doing any dumb "copying" of
anything into the resultant structure. But even if he does, that's still
beside the point. As far as I'm concerned, inside 'l_SYM_2B' he is
somehow _building_ the new 'struct v1' from 'a' (and I don't know what
the type of 'a' is). How exactly he is doing it inside 'l_SYM_2B' - I
don't know, but it is irrelevant anyway.

Note that OP's original code with intermediate value 'val1' did two
things: firstly, it had to form the result of 'l_SYM_2B' function (it is
done inside 'l_SYM_2B' and I, once again, don't know and don't care how
it is done, by "heavy copying" or in some other way), and secondly, the
returned result was copied to variable 'val1', which is definitely a
"heavy copying". The approach that I propose eliminates the second heavy
copying, it builds the value of 'val1' "in place" (through a pointer).
That's the entire point of this approach.

A smart optimizing compiler might be good enough to do the same thing
with the original code automatically. But just to be sure one can do it
manually. I don't see how this can impede any compiler optimizations.
Can you please elaborate?

Whether the first step (forming of the new 'struct v1' value from 'a')
can be optimized is a separate question. So far we didn't even touch it
in this discussion.

--
Best regards,
Andrey Tarasevich

Nov 14 '05 #16
Hi, Adam!

Adam Warner wrote:
On Wed, 29 Dec 2004 15:27:27 +0100, Dietmar Schindler wrote:
* Footnote:
a is a local variable of type struct o.
l_SYM_2B(struct o * arg0) returns struct v1, defined as { struct o o[1]; };
print_aesthetic has the prototype struct v1 print_aesthetic (struct o *);
If l_SYM_2B is a function, you may get an intermediate copy of the
structure anyway. ...

l_SYM_2B() returns the structure v1, i.e. leaves room for one object o on
the stack, aligned to the same position as if struct o itself was returned.

I sincerely hope no compiler will ever create a copy of struct o before
returning its address. I am only using the struct v1 for symmetry with v2,
v3, etc. The address of the first object o is expected to be the same as
the v1 structure. As I am only obtaining the address of o[0] within the
structure v1 I cannot see how C semantics could result in an intermediate
copy of o.


I had already typed in a different reply, when I realized where our
misunderstandin g might be. By "intermedia te copy of the structure", I
meant your abovementioned object o (since this is already a copy of v1)
itself, not a copy of o. If you mean it reversely, then we agree.

Regards,
Dietmar
Nov 14 '05 #17
Andrey Tarasevich wrote:
Dietmar Schindler wrote:
Andrey Tarasevich wrote:
Another approach would be to pass a pointer to the destination structure
to 'l_SYM_2B' function as a separate parameter and fill it in inside
'l_SYM_2B' instead of returning it from 'l_SYM_2B' by value. In other
words, you can (if you can) change the signature of 'l_SYM_2B' to
something like

void l_SYM_2B(<whate ver>* lp_a, struct v1* lp_v1)
{
/* fill in '*lp_v1' accordingly */
}

Your original code in this case would look as follows

struct v1 ;
l_SYM_2B(&a, &val1);
print_aesthetic (&val1.o[0]);

No heavy copying is performed.
Still l_SYM_2B has to copy an entire struct v1 into *lp_v1. That is
heavy copying (and, moreover, leaving compilers less opportunities to
optimize it away).
...


As I understood it, the OP is not actually doing any dumb "copying" of
anything into the resultant structure. But even if he does, that's still
beside the point. As far as I'm concerned, inside 'l_SYM_2B' he is
somehow _building_ the new 'struct v1' from 'a' (and I don't know what
the type of 'a' is). How exactly he is doing it inside 'l_SYM_2B' - I
don't know, but it is irrelevant anyway.


According to what Adam Warner (the OP) wrote in a followup about
l_SYM_2B ("It
is to be my dynamic Lisp function +"), it seems you understood it right.
I was referring to the (in the original post not ruled out) case where
l_SYM_2B just returned an existing object, and that was not "beside the
point" (see below).
Note that OP's original code with intermediate value 'val1' did two
things: firstly, it had to form the result of 'l_SYM_2B' function (it is
done inside 'l_SYM_2B' and I, once again, don't know and don't care how
it is done, by "heavy copying" or in some other way), and secondly, the
returned result was copied to variable 'val1', which is definitely a
"heavy copying". The approach that I propose eliminates the second heavy
copying, it builds the value of 'val1' "in place" (through a pointer).
That's the entire point of this approach.

A smart optimizing compiler might be good enough to do the same thing
with the original code automatically. But just to be sure one can do it
manually. I don't see how this can impede any compiler optimizations.
Can you please elaborate?

Whether the first step (forming of the new 'struct v1' value from 'a')
can be optimized is a separate question. So far we didn't even touch it
in this discussion.


I meant exactly this "first step". Since we had no conversation about
dividing the original problem (which included avoiding the creation of
intermediate copies of the structure) into distinct questions or steps,
I don't think we can say that we "didn't even touch it in this
discussion" - I certainly did.

You, too, seem to suspect that forming of the new 'struct v1' value from
'a' can be optimized in certain cases (which, as we know now, are
probably different from the matter in hand). May I assume that you do
see how passing a pointer to a to-be-filled structure can impede an
optimization in such cases, and there is no need to elaborate on that?

Best regards,
Dietmar Schindler
Nov 14 '05 #18
On Wed, 29 Dec 2004 18:31:17 -0800, Andrey Tarasevich wrote:

....
I would still like to know whether the following code

struct S { int i[5]; };

struct S foo() { struct S s = { 0 }; return s; }

int main() { int i = foo().i[1]; }

is valid C89/C90? Comeau thinks it is not (and the standard, as I
understand it, seems to support this position). GCC, on the other hand,
accepts it. Can anyone comment on it?


Here gcc -ansi -pedantic gives:

retval.c:5: warning: ISO C89 forbids subscripting non-lvalue array

So gcc also generates an appropriate diagnostic.

Lawrence

Nov 14 '05 #19
Dietmar Schindler wrote:
>> ...
>> Your original code in this case would look as follows
>>
>> struct v1 val1;
>> l_SYM_2B(&a, &val1);
>> print_aesthetic (&val1.o[0]);
>>
>> No heavy copying is performed.
>
> Still l_SYM_2B has to copy an entire struct v1 into *lp_v1. That is
> heavy copying (and, moreover, leaving compilers less opportunities to
> optimize it away).
> ...
As I understood it, the OP is not actually doing any dumb "copying" of
anything into the resultant structure. But even if he does, that's still
beside the point. As far as I'm concerned, inside 'l_SYM_2B' he is
somehow _building_ the new 'struct v1' from 'a' (and I don't know what
the type of 'a' is). How exactly he is doing it inside 'l_SYM_2B' - I
don't know, but it is irrelevant anyway.


According to what Adam Warner (the OP) wrote in a followup about
l_SYM_2B ("It
is to be my dynamic Lisp function +"), it seems you understood it right.
I was referring to the (in the original post not ruled out) case where
l_SYM_2B just returned an existing object, and that was not "beside the
point" (see below).


Oh, if 'l_SYM_2B' returns an exact copy of an existing object (i.e. an
object that is not local to function 'l_SYM_2B'), then, of course, the
situation can be perceived differently. In that case, maybe, it could be
made to return a [const-qualified] pointer to the object

const struct v1* l_SYM_2B(<whate ver>* lp_a);

and the original code would look as follows

print_aesthetic (&l_SYM_2B(&a )->o[0]);

thus eliminating all copying entirely.

But frankly, I don't see where Adam says that this function returns an
existing object (I did re-read his follow-up message you mention). All
the time I was assuming that every invocation of 'l_SYM_2B' is supposed
to build and return a new instance of 'struct v1'. I could be wrong, of
course.
...
You, too, seem to suspect that forming of the new 'struct v1' value from
'a' can be optimized in certain cases (which, as we know now, are
probably different from the matter in hand). May I assume that you do
see how passing a pointer to a to-be-filled structure can impede an
optimization in such cases, and there is no need to elaborate on that?
...


I'm not saying that it can't impede optimization, but at the same time
I'd be glad to see a [simplified] example that would illustrate the
particular optimization you have in mind.

If you are talking about the above situation with 'l_SYM_2B' always
returning "an existing object", then, of course, it can be optimized.
However, I can only see how it can be done manually, and somehow I'm not
sure that we can realistically expect this kind of optimization from the
compiler.

--
Best regards,
Andrey Tarasevich
Nov 14 '05 #20

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

6
3529
by: Adrian | last post by:
I am trying to pass the address of a C++ function into a Fortran routine to enable the Fortran routine to call this C++ function. I have to do it this way as our build process does not allow circular dependencies of DLL's. Does anyone know how to do this, I have tried everything in my book. I have a C++ function GetP: DllExport void GetP()
117
11894
by: Steevo | last post by:
Any suggestions as to the best programs for cloaking email addresses? Many thanks -- Steevo
4
5524
by: Tom Warren | last post by:
About once a year or so for the last 10 years, I update my street address parser and I'm starting to look at it again. This parser splits a street address line into its smallest common elements (number, trailer, pre, name, suffix, post, unit, unit id). I always start this update process by searching Google-Groups and Google-web for anything new out there, but there is never very much. Has anyone run into anything in their travels?...
5
2337
by: Adrian | last post by:
I am trying to pass the address of a C++ function into a Fortran routine to enable the Fortran routine to call this C++ function. I have to do it this way as our build process does not allow circular dependencies of DLL's. Does anyone know how to do this, I have tried everything in my book. I have a C++ function GetP: DllExport void GetP()
7
5827
by: ashu | last post by:
look at code #include<stdio.h> int *mult(void); int main(void) { int *ptr,i; ptr=mult; for(i=0;i<6;i++) { printf("%d",*(ptr++));
4
3275
by: Paul Jansen | last post by:
Don't go away yet... this is a bit more complicated than 'how do I take a method address'! I have a class foo, and method foo::bar. I also have a tree of foo objects, which I process recursively, in top-down or bottom-up passes. When I traverse the tree and reach object X, I need to call X.bar(). 'bar' is not static; it needs the data in the current object/node. Furthermore, I have generic (recursive) functions to traverse the tree....
1
1453
by: edaddyj | last post by:
Hello, I have a db that tracks Jobs that our company does. Each job has a unique JobID. I have a report that groups all my jobs by JobStreetAddress. Since some jobs may be at the same address I want a command button in my Jobs form that opens my report (repair report) just showing the jobs with that address. Here is what I have that isn't working: Private Sub Command157_Click() On Error GoTo Err_Command157_Click Dim stDocName As...
7
2813
by: John Koleszar | last post by:
Hi all, I'm porting some code that provides compile-time assertions from one compiler to another and ran across what I believe to be compliant code that won't compile using the new compiler. Not naming names here to remove bias - I'm trying to tell if I'm relying on implementation defined behavior or if this is a bug in the new compiler. Consider this stripped down example:
7
9659
by: Guillaume Dargaud | last post by:
Hello all, I have an example of working code under my eyes that goes as follow: unsigned long address=0x400000; (void (*)(void)address)(); It's supposed to jump start a kernel loaded at that address from a small bootloader. But my cross compiler chokes on the second line (89) and I must say I've
0
10363
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
10164
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
1
10110
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
9961
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
5397
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
5534
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
1
4066
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
2
3669
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
3
2894
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.